Removed dateline normalization
Also removed an rtree wrapper that's no longer needed along with the Z rectange coordinates for rtree items.
This commit is contained in:
parent
db7d8972aa
commit
565f32cc5b
@ -37,13 +37,13 @@ func (i *itemT) Less(item btree.Item, ctx interface{}) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *itemT) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64) {
|
func (i *itemT) Rect() (minX, minY, maxX, maxY float64) {
|
||||||
bbox := i.object.CalculatedBBox()
|
bbox := i.object.CalculatedBBox()
|
||||||
return bbox.Min.X, bbox.Min.Y, bbox.Min.Z, bbox.Max.X, bbox.Max.Y, bbox.Max.Z
|
return bbox.Min.X, bbox.Min.Y, bbox.Max.X, bbox.Max.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *itemT) Point() (x, y, z float64) {
|
func (i *itemT) Point() (x, y float64) {
|
||||||
x, y, z, _, _, _ = i.Rect()
|
x, y, _, _ = i.Rect()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,7 +375,7 @@ func (c *Collection) ScanGreaterOrEqual(id string, desc bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Collection) geoSearch(bbox geojson.BBox, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
|
func (c *Collection) geoSearch(bbox geojson.BBox, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
|
||||||
return c.index.Search(bbox.Min.Y, bbox.Min.X, bbox.Max.Y, bbox.Max.X, bbox.Min.Z, bbox.Max.Z, func(item interface{}) bool {
|
return c.index.Search(bbox.Min.X, bbox.Min.Y, bbox.Max.X, bbox.Max.Y, func(item interface{}) bool {
|
||||||
iitm := item.(*itemT)
|
iitm := item.(*itemT)
|
||||||
if !iterator(iitm.id, iitm.object, c.getFieldValues(iitm.id)) {
|
if !iterator(iitm.id, iitm.object, c.getFieldValues(iitm.id)) {
|
||||||
return false
|
return false
|
||||||
@ -587,7 +587,7 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Collection) NearestNeighbors(lat, lon float64, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
|
func (c *Collection) NearestNeighbors(lat, lon float64, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
|
||||||
return c.index.NearestNeighbors(lat, lon, func(item interface{}) bool {
|
return c.index.KNN(lon, lat, func(item interface{}) bool {
|
||||||
var iitm *itemT
|
var iitm *itemT
|
||||||
iitm, ok := item.(*itemT)
|
iitm, ok := item.(*itemT)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -1,205 +1,84 @@
|
|||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
rtree "github.com/tidwall/tile38/pkg/index/rtree"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tidwall/tile38/pkg/index/rtree"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Item represents an index item.
|
|
||||||
type Item interface {
|
|
||||||
Point() (x, y, z float64)
|
|
||||||
Rect() (minX, minY, minZ, maxX, maxY, maxZ float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlexItem can represent a point or a rectangle
|
|
||||||
type FlexItem struct {
|
|
||||||
MinX, MinY, MinZ, MaxX, MaxY, MaxZ float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rect returns the rectangle
|
|
||||||
func (item *FlexItem) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64) {
|
|
||||||
return item.MinX, item.MinY, item.MinZ, item.MaxX, item.MaxY, item.MaxZ
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point returns the point
|
|
||||||
func (item *FlexItem) Point() (x, y, z float64) {
|
|
||||||
return item.MinX, item.MinY, item.MinZ
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index is a geospatial index
|
// Index is a geospatial index
|
||||||
type Index struct {
|
type Index struct {
|
||||||
r *rtree.RTree
|
r *rtree.RTree
|
||||||
nr map[*rtree.Rect]Item // normalized points
|
|
||||||
nrr map[Item][]*rtree.Rect // normalized points
|
|
||||||
mulm map[interface{}]bool // store items that contain multiple rects
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New create a new index
|
// New create a new index
|
||||||
func New() *Index {
|
func New() *Index {
|
||||||
return &Index{
|
return &Index{
|
||||||
r: rtree.New(),
|
r: rtree.New(),
|
||||||
mulm: make(map[interface{}]bool),
|
|
||||||
nr: make(map[*rtree.Rect]Item),
|
|
||||||
nrr: make(map[Item][]*rtree.Rect),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Item represents an index item.
|
||||||
|
type Item interface {
|
||||||
|
Point() (x, y float64)
|
||||||
|
Rect() (minX, minY, maxX, maxY float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
// Insert inserts an item into the index
|
// Insert inserts an item into the index
|
||||||
func (ix *Index) Insert(item Item) {
|
func (ix *Index) Insert(item Item) {
|
||||||
minX, minY, minZ, maxX, maxY, maxZ := item.Rect()
|
minX, minY, maxX, maxY := item.Rect()
|
||||||
if minX == maxX && minY == maxY {
|
ix.r.Insert([2]float64{minX, minY}, [2]float64{maxX, maxY}, item)
|
||||||
x, y, normd := normPoint(minY, minX)
|
|
||||||
if normd {
|
|
||||||
nitem := &rtree.Rect{MinX: x, MinY: y, MinZ: minZ, MaxX: x, MaxY: y, MaxZ: maxZ}
|
|
||||||
ix.nr[nitem] = item
|
|
||||||
ix.nrr[item] = []*rtree.Rect{nitem}
|
|
||||||
ix.r.Insert(nitem)
|
|
||||||
} else {
|
|
||||||
ix.r.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, MinZ: minZ, MaxX: maxX, MaxY: maxY, MaxZ: maxZ}
|
|
||||||
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
|
// Remove removed an item from the index
|
||||||
func (ix *Index) Remove(item Item) {
|
func (ix *Index) Remove(item Item) {
|
||||||
if nitems, ok := ix.nrr[item]; ok {
|
minX, minY, maxX, maxY := item.Rect()
|
||||||
for _, nitem := range nitems {
|
ix.r.Remove([2]float64{minX, minY}, [2]float64{maxX, maxY}, item)
|
||||||
ix.r.Remove(nitem)
|
|
||||||
delete(ix.nr, nitem)
|
|
||||||
}
|
|
||||||
delete(ix.nrr, item)
|
|
||||||
} else {
|
|
||||||
ix.r.Remove(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count counts all items in the index.
|
// Count counts all items in the index.
|
||||||
func (ix *Index) Count() int {
|
func (ix *Index) Count() int {
|
||||||
count := 0
|
return ix.r.Count()
|
||||||
ix.Search(-90, -180, 90, 180, math.Inf(-1), math.Inf(+1), func(_ interface{}) bool {
|
|
||||||
count++
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bounds returns the minimum bounding rectangle of all items in the index.
|
// Bounds returns the minimum bounding rectangle of all items in the index.
|
||||||
func (ix *Index) Bounds() (MinX, MinY, MaxX, MaxY float64) {
|
func (ix *Index) Bounds() (MinX, MinY, MaxX, MaxY float64) {
|
||||||
return ix.r.Bounds()
|
min, max := ix.r.Bounds()
|
||||||
|
return min[0], min[1], max[0], max[1]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAll removes all items from the index.
|
// RemoveAll removes all items from the index.
|
||||||
func (ix *Index) RemoveAll() {
|
func (ix *Index) RemoveAll() {
|
||||||
ix.r.RemoveAll()
|
ix.r = rtree.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
type UintptrInterface struct {
|
func (ix *Index) KNN(x, y float64, iterator func(item interface{}) bool) bool {
|
||||||
Type uintptr
|
return ix.r.KNN([2]float64{x, y}, [2]float64{x, y}, true,
|
||||||
Ptr uintptr
|
func(item interface{}, dist float64) bool {
|
||||||
}
|
return iterator(item)
|
||||||
type UnsafePointerInterface struct {
|
})
|
||||||
Type uintptr
|
|
||||||
Ptr unsafe.Pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUintptrInterface(v interface{}) UintptrInterface {
|
|
||||||
return *(*UintptrInterface)(unsafe.Pointer(&v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUnsafePointerInterface(v interface{}) UnsafePointerInterface {
|
|
||||||
return *(*UnsafePointerInterface)(unsafe.Pointer(&v))
|
|
||||||
}
|
|
||||||
|
|
||||||
var rectType = func() uintptr {
|
|
||||||
var rrrr rtree.Rect
|
|
||||||
return GetUintptrInterface(&rrrr).Type
|
|
||||||
}()
|
|
||||||
|
|
||||||
func (ix *Index) getRTreeItem(item interface{}) interface{} {
|
|
||||||
uzi := GetUnsafePointerInterface(item)
|
|
||||||
if uzi.Type == rectType {
|
|
||||||
return ix.nr[(*rtree.Rect)(uzi.Ptr)]
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ix *Index) NearestNeighbors(lat, lon float64, iterator func(item interface{}) bool) bool {
|
|
||||||
x, y, _ := normPoint(lat, lon)
|
|
||||||
return ix.r.NearestNeighbors(x, y, func(item interface{}, dist float64) bool {
|
|
||||||
return iterator(ix.getRTreeItem(item))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search returns all items that intersect the bounding box.
|
// Search returns all items that intersect the bounding box.
|
||||||
func (ix *Index) Search(swLat, swLon, neLat, neLon, minZ, maxZ float64,
|
func (ix *Index) Search(minX, minY, maxX, maxY float64,
|
||||||
iterator func(item interface{}) bool,
|
iterator func(item interface{}) bool,
|
||||||
) bool {
|
) bool {
|
||||||
var keepon = true
|
return ix.r.Search([2]float64{minX, minY}, [2]float64{maxX, maxY},
|
||||||
var idm = make(map[interface{}]bool)
|
func(item interface{}) bool {
|
||||||
mins, maxs, _ := normRect(swLat, swLon, neLat, neLon)
|
return iterator(item)
|
||||||
// Points
|
})
|
||||||
if len(mins) == 1 {
|
|
||||||
// There is only one rectangle.
|
|
||||||
// It's possible that a r rect may span multiple entries. Check mulm map for spanning rects.
|
|
||||||
if keepon {
|
|
||||||
ix.r.Search(mins[0][0], mins[0][1], minZ, maxs[0][0], maxs[0][1], maxZ,
|
|
||||||
func(v interface{}) bool {
|
|
||||||
item := ix.getRTreeItem(v)
|
|
||||||
if len(ix.mulm) > 0 && ix.mulm[item] {
|
|
||||||
if !idm[item] {
|
|
||||||
idm[item] = true
|
|
||||||
keepon = iterator(item)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keepon = iterator(item)
|
|
||||||
}
|
|
||||||
return keepon
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// There are multiple rectangles. Duplicates might occur.
|
|
||||||
for i := range mins {
|
|
||||||
if keepon {
|
|
||||||
ix.r.Search(mins[i][0], mins[i][1], minZ, maxs[i][0], maxs[i][1], maxZ,
|
|
||||||
func(item interface{}) bool {
|
|
||||||
iitm := ix.getRTreeItem(item)
|
|
||||||
if iitm != nil {
|
|
||||||
if ix.mulm[iitm] {
|
|
||||||
if !idm[iitm] {
|
|
||||||
idm[iitm] = true
|
|
||||||
keepon = iterator(iitm)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keepon = iterator(iitm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keepon
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keepon
|
|
||||||
}
|
}
|
||||||
|
@ -8,48 +8,54 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
seed := time.Now().UnixNano()
|
||||||
|
fmt.Printf("seed: %d\n", seed)
|
||||||
|
rand.Seed(seed)
|
||||||
|
}
|
||||||
|
|
||||||
func randf(min, max float64) float64 {
|
func randf(min, max float64) float64 {
|
||||||
return rand.Float64()*(max-min) + min
|
return rand.Float64()*(max-min) + min
|
||||||
}
|
}
|
||||||
|
|
||||||
func randPoint() (lat float64, lon float64) {
|
func randRect() (minX, minY, maxX, maxY float64) {
|
||||||
// intentionally go out of range.
|
minX, minY = rand.Float64()*360-180, rand.Float64()*180-90
|
||||||
return randf(-100, 100), randf(-190, 190)
|
maxX, maxY = rand.Float64()*360-180, rand.Float64()*180-90
|
||||||
}
|
if minX > maxX {
|
||||||
|
minX, maxX = maxX, minX
|
||||||
func randRect() (swLat, swLon, neLat, neLon float64) {
|
}
|
||||||
swLat, swLon = randPoint()
|
if minY > maxY {
|
||||||
// intentionally go out of range even more.
|
minY, maxY = maxY, minY
|
||||||
neLat = randf(swLat-10, swLat+10)
|
}
|
||||||
neLon = randf(swLon-10, swLon+10)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func wp(swLat, swLon, neLat, neLon float64) *FlexItem {
|
func wp(minX, minY, maxX, maxY float64) *FlexItem {
|
||||||
return &FlexItem{
|
return &FlexItem{
|
||||||
MinX: swLon,
|
MinX: minX,
|
||||||
MinY: swLat,
|
MinY: minY,
|
||||||
MaxX: neLon,
|
MaxX: maxX,
|
||||||
MaxY: neLat,
|
MaxY: maxY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRandomInserts(t *testing.T) {
|
func TestRandomInserts(t *testing.T) {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
l := 200000
|
|
||||||
tr := New()
|
|
||||||
start := time.Now()
|
|
||||||
i := 0
|
|
||||||
for ; i < l/2; i++ {
|
|
||||||
swLat, swLon := randPoint()
|
|
||||||
tr.Insert(wp(swLat, swLon, swLat, swLon))
|
|
||||||
}
|
|
||||||
inspdur := time.Now().Sub(start)
|
|
||||||
|
|
||||||
start = time.Now()
|
l := 100000
|
||||||
|
tr := New()
|
||||||
|
i := 0
|
||||||
|
var gitems []*FlexItem
|
||||||
|
var nitems []*FlexItem
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
for ; i < l; i++ {
|
for ; i < l; i++ {
|
||||||
swLat, swLon, neLat, neLon := randRect()
|
item := wp(randRect())
|
||||||
tr.Insert(wp(swLat, swLon, neLat, neLon))
|
tr.Insert(item)
|
||||||
|
if item.MinX >= -180 && item.MinY >= -90 && item.MaxX <= 180 && item.MaxY <= 90 {
|
||||||
|
gitems = append(gitems, item)
|
||||||
|
} else {
|
||||||
|
nitems = append(nitems, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
insrdur := time.Now().Sub(start)
|
insrdur := time.Now().Sub(start)
|
||||||
count := 0
|
count := 0
|
||||||
@ -60,17 +66,18 @@ func TestRandomInserts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
count = 0
|
count = 0
|
||||||
items := make([]Item, 0, l)
|
items := make([]Item, 0, l)
|
||||||
tr.Search(-90, -180, 90, 180, 0, 0, func(item interface{}) bool {
|
tr.Search(-180, -90, +180, +90, func(item interface{}) bool {
|
||||||
count++
|
count++
|
||||||
items = append(items, item.(Item))
|
items = append(items, item.(Item))
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
if count != l {
|
|
||||||
t.Fatalf("count == %d, expect %d", count, l)
|
if count != len(gitems) {
|
||||||
|
t.Fatalf("count == %d, expect %d", count, len(gitems))
|
||||||
}
|
}
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
count1 := 0
|
count1 := 0
|
||||||
tr.Search(33, -115, 34, -114, 0, 0, func(item interface{}) bool {
|
tr.Search(33, -115, 34, -114, func(item interface{}) bool {
|
||||||
count1++
|
count1++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@ -79,7 +86,7 @@ func TestRandomInserts(t *testing.T) {
|
|||||||
start = time.Now()
|
start = time.Now()
|
||||||
count2 := 0
|
count2 := 0
|
||||||
|
|
||||||
tr.Search(33-180, -115-360, 34-180, -114-360, 0, 0, func(item interface{}) bool {
|
tr.Search(33-180, -115-360, 34-180, -114-360, func(item interface{}) bool {
|
||||||
count2++
|
count2++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@ -87,28 +94,27 @@ func TestRandomInserts(t *testing.T) {
|
|||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
count3 := 0
|
count3 := 0
|
||||||
tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
|
tr.Search(-10, 170, 20, 200, func(item interface{}) bool {
|
||||||
count3++
|
count3++
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
searchdur3 := time.Now().Sub(start)
|
searchdur3 := time.Now().Sub(start)
|
||||||
|
|
||||||
fmt.Printf("Randomly inserted %d points in %s.\n", l/2, inspdur.String())
|
fmt.Printf("Randomly inserted %d rects in %s.\n", l, insrdur.String())
|
||||||
fmt.Printf("Randomly inserted %d rects in %s.\n", l/2, insrdur.String())
|
|
||||||
fmt.Printf("Searched %d items in %s.\n", count1, searchdur1.String())
|
fmt.Printf("Searched %d items in %s.\n", count1, searchdur1.String())
|
||||||
fmt.Printf("Searched %d items in %s.\n", count2, searchdur2.String())
|
fmt.Printf("Searched %d items in %s.\n", count2, searchdur2.String())
|
||||||
fmt.Printf("Searched %d items in %s.\n", count3, searchdur3.String())
|
fmt.Printf("Searched %d items in %s.\n", count3, searchdur3.String())
|
||||||
|
|
||||||
tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
|
tr.Search(-10, 170, 20, 200, func(item interface{}) bool {
|
||||||
lat1, lon1, _, lat2, lon2, _ := item.(Item).Rect()
|
lat1, lon1, lat2, lon2 := item.(Item).Rect()
|
||||||
if lat1 == lat2 && lon1 == lon2 {
|
if lat1 == lat2 && lon1 == lon2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
|
tr.Search(-10, 170, 20, 200, func(item interface{}) bool {
|
||||||
lat1, lon1, _, lat2, lon2, _ := item.(Item).Rect()
|
lat1, lon1, lat2, lon2 := item.(Item).Rect()
|
||||||
if lat1 != lat2 || lon1 != lon2 {
|
if lat1 != lat2 || lon1 != lon2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -130,9 +136,9 @@ func TestRandomInserts(t *testing.T) {
|
|||||||
t.Fatal("getQTreeItem(nil) should return nil")
|
t.Fatal("getQTreeItem(nil) should return nil")
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
if tr.getRTreeItem(nil) != nil {
|
// if tr.getRTreeItem(nil) != nil {
|
||||||
t.Fatal("getRTreeItem(nil) should return nil")
|
// t.Fatal("getRTreeItem(nil) should return nil")
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemory(t *testing.T) {
|
func TestMemory(t *testing.T) {
|
||||||
@ -173,7 +179,7 @@ func TestInsertVarious(t *testing.T) {
|
|||||||
t.Fatalf("count = %d, expect 1", count)
|
t.Fatalf("count = %d, expect 1", count)
|
||||||
}
|
}
|
||||||
found := false
|
found := false
|
||||||
tr.Search(-90, -180, 90, 180, 0, 0, func(item2 interface{}) bool {
|
tr.Search(-90, -180, 90, 180, func(item2 interface{}) bool {
|
||||||
if item2.(Item) == item {
|
if item2.(Item) == item {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
package index
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
// normPoint takes the latitude and longitude of one point and return the x,y position on a world map.
|
|
||||||
// The map bounds are minimum -180,-90 and maximum 180,90. These values are x,y; not lat,lon.
|
|
||||||
func normPoint(lat, lon float64) (x, y float64, normd bool) {
|
|
||||||
// Check if the rect is completely in bounds.
|
|
||||||
// This is likely to be the vast majority of cases.
|
|
||||||
if lon >= -180 && lon <= 180 && lat >= -90 && lat <= 90 {
|
|
||||||
return lon, lat, false
|
|
||||||
}
|
|
||||||
lat = math.Mod(lat, 360)
|
|
||||||
for lat < -90 || lat > 90 {
|
|
||||||
if lat < -90 {
|
|
||||||
lat = -90 - (90 + lat)
|
|
||||||
lon = 180 + lon
|
|
||||||
}
|
|
||||||
if lat > 90 {
|
|
||||||
lat = 90 + (90 - lat)
|
|
||||||
lon = 180 + lon
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lon = math.Mod(lon, 360)
|
|
||||||
for lon < -180 {
|
|
||||||
lon += 360
|
|
||||||
}
|
|
||||||
for lon > 180 {
|
|
||||||
lon -= 360
|
|
||||||
}
|
|
||||||
return lon, lat, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// normRect takes the latitude and longitude of two points which define a rectangle and returns an array of x,y rectangles on a world map.
|
|
||||||
// The map bounds are minimum -180,-90 and maximum 180,90. These values are x,y; not lat,lon.
|
|
||||||
func normRect(swLat, swLon, neLat, neLon float64) (mins, maxs [][]float64, normd bool) {
|
|
||||||
mins, maxs, normd = normRectStep(swLat, swLon, neLat, neLon, nil, nil, false)
|
|
||||||
return mins, maxs, normd
|
|
||||||
}
|
|
||||||
|
|
||||||
func normRectStep(swLat, swLon, neLat, neLon float64, mins, maxs [][]float64, normd bool) (minsOut, maxsOut [][]float64, normdOut bool) {
|
|
||||||
// Make sure that the northeast point is greater than the southwest point.
|
|
||||||
if neLat < swLat {
|
|
||||||
swLat, neLat, normd = neLat, swLat, true
|
|
||||||
}
|
|
||||||
if neLon < swLon {
|
|
||||||
swLon, neLon, normd = neLon, swLon, true
|
|
||||||
}
|
|
||||||
if swLon < -180 || neLon > 180 {
|
|
||||||
// The rect is horizontally out of bounds.
|
|
||||||
if neLon-swLon > 360 {
|
|
||||||
// The rect goes around the world. Just normalize to -180 to 180.
|
|
||||||
swLon = -180
|
|
||||||
neLon = 180
|
|
||||||
} else if swLon < -180 && neLon < -180 {
|
|
||||||
// The rect is way left. Move it into range.
|
|
||||||
// TODO: replace loops with math/mod.
|
|
||||||
for {
|
|
||||||
swLon += 360
|
|
||||||
neLon += 360
|
|
||||||
if swLon >= -180 || neLon >= -180 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if swLon > 180 && neLon > 180 {
|
|
||||||
// The rect is way right. Move it into range.
|
|
||||||
// TODO: replace loops with math/mod.
|
|
||||||
for {
|
|
||||||
swLon -= 360
|
|
||||||
neLon -= 360
|
|
||||||
if swLon <= 180 || neLon <= 180 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The rect needs to be split into two.
|
|
||||||
if swLon < -180 {
|
|
||||||
mins, maxs, normd = normRectStep(swLat, 180+(180+swLon), neLat, 180, mins, maxs, normd)
|
|
||||||
mins, maxs, normd = normRectStep(swLat, -180, neLat, neLon, mins, maxs, normd)
|
|
||||||
} else if neLon > 180 {
|
|
||||||
mins, maxs, normd = normRectStep(swLat, swLon, neLat, 180, mins, maxs, normd)
|
|
||||||
mins, maxs, normd = normRectStep(swLat, -180, neLat, -180+(neLon-180), mins, maxs, normd)
|
|
||||||
} else {
|
|
||||||
panic("should not be reached")
|
|
||||||
}
|
|
||||||
return mins, maxs, true
|
|
||||||
}
|
|
||||||
return normRectStep(swLat, swLon, neLat, neLon, mins, maxs, true)
|
|
||||||
} else if swLat < -90 || neLat > 90 {
|
|
||||||
// The rect is vertically out of bounds.
|
|
||||||
if neLat-swLat > 360 {
|
|
||||||
// The rect goes around the world. Just normalize to -180 to 180.
|
|
||||||
swLat = -180
|
|
||||||
neLat = 180
|
|
||||||
} else if swLat < -90 && neLat < -90 {
|
|
||||||
swLat = -90 + (-90 - swLat)
|
|
||||||
neLat = -90 + (-90 - neLat)
|
|
||||||
swLon = swLon - 180
|
|
||||||
neLon = neLon - 180
|
|
||||||
} else if swLat > 90 && neLat > 90 {
|
|
||||||
swLat = 90 - (swLat - 90)
|
|
||||||
neLat = 90 - (neLat - 90)
|
|
||||||
swLon = swLon - 180
|
|
||||||
neLon = neLon - 180
|
|
||||||
} else {
|
|
||||||
if neLat > 90 {
|
|
||||||
mins, maxs, normd = normRectStep(swLat, swLon, 90, neLon, mins, maxs, normd)
|
|
||||||
mins, maxs, normd = normRectStep(90-(neLat-90), swLon-180, 90, neLon-180, mins, maxs, normd)
|
|
||||||
} else if swLat < -90 {
|
|
||||||
mins, maxs, normd = normRectStep(-90, swLon, neLat, neLon, mins, maxs, normd)
|
|
||||||
mins, maxs, normd = normRectStep(-90, swLon-180, -90-(90+swLat), neLon-180, mins, maxs, normd)
|
|
||||||
} else {
|
|
||||||
panic("should not be reached")
|
|
||||||
}
|
|
||||||
return mins, maxs, true
|
|
||||||
}
|
|
||||||
return normRectStep(swLat, swLon, neLat, neLon, mins, maxs, true)
|
|
||||||
} else {
|
|
||||||
// rect is completely in bounds.
|
|
||||||
mins = append(mins, []float64{swLon, swLat})
|
|
||||||
maxs = append(maxs, []float64{neLon, neLat})
|
|
||||||
return mins, maxs, normd
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +1,7 @@
|
|||||||
// Package rtreebase
|
// This package is adapted from the RBush project by Vladimir Agafonkin.
|
||||||
// This package is a port of the fantastic RBush project by Vladimir Agafonkin.
|
|
||||||
// https://github.com/mourner/rbush
|
// https://github.com/mourner/rbush
|
||||||
//
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016 Vladimir Agafonkin
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
package rtreebase
|
package rtree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
@ -1,4 +1,4 @@
|
|||||||
package rtreebase
|
package rtree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,32 +1,11 @@
|
|||||||
// Package rtreebase
|
// This package is adapted from the RBush project by Vladimir Agafonkin.
|
||||||
// This package is a port of the fantastic RBush project by Vladimir Agafonkin.
|
|
||||||
// https://github.com/mourner/rbush
|
// https://github.com/mourner/rbush
|
||||||
//
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016 Vladimir Agafonkin
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
package rtreebase
|
package rtree
|
||||||
|
|
||||||
import "github.com/tidwall/tinyqueue"
|
import (
|
||||||
|
"github.com/tidwall/tinyqueue"
|
||||||
|
)
|
||||||
|
|
||||||
type queueItem struct {
|
type queueItem struct {
|
||||||
node *treeNode
|
node *treeNode
|
@ -1,32 +1,11 @@
|
|||||||
// Package rtreebase
|
// This package is adapted from the RBush project by Vladimir Agafonkin.
|
||||||
// This package is a port of the fantastic RBush project by Vladimir Agafonkin.
|
|
||||||
// https://github.com/mourner/rbush
|
// https://github.com/mourner/rbush
|
||||||
//
|
|
||||||
// MIT License
|
|
||||||
//
|
|
||||||
// Copyright (c) 2016 Vladimir Agafonkin
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
package rtreebase
|
package rtree
|
||||||
|
|
||||||
import "math"
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
// Load bulk load items into the R-tree.
|
// Load bulk load items into the R-tree.
|
||||||
func (tr *RTree) Load(mins, maxs [][D]float64, items []interface{}) {
|
func (tr *RTree) Load(mins, maxs [][D]float64, items []interface{}) {
|
@ -1,77 +0,0 @@
|
|||||||
package rtree
|
|
||||||
|
|
||||||
import "github.com/tidwall/tile38/pkg/index/rtreebase"
|
|
||||||
|
|
||||||
// Item is an rtree item
|
|
||||||
type Item interface {
|
|
||||||
Rect() (minX, minY, minZ, maxX, maxY, maxZ float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rect is a rectangle
|
|
||||||
type Rect struct {
|
|
||||||
MinX, MinY, MinZ, MaxX, MaxY, MaxZ float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rect returns the rectangle
|
|
||||||
func (item *Rect) Rect() (minX, minY, minZ, maxX, maxY, maxZ float64) {
|
|
||||||
return item.MinX, item.MinY, item.MinZ, item.MaxX, item.MaxY, item.MaxZ
|
|
||||||
}
|
|
||||||
|
|
||||||
// RTree is an implementation of an rtree
|
|
||||||
type RTree struct {
|
|
||||||
tr *rtreebase.RTree
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new RTree
|
|
||||||
func New() *RTree {
|
|
||||||
return &RTree{
|
|
||||||
tr: rtreebase.New(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert inserts item into rtree
|
|
||||||
func (tr *RTree) Insert(item Item) {
|
|
||||||
minX, minY, _, maxX, maxY, _ := item.Rect()
|
|
||||||
tr.tr.Insert([2]float64{minX, minY}, [2]float64{maxX, maxY}, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes item from rtree
|
|
||||||
func (tr *RTree) Remove(item Item) {
|
|
||||||
minX, minY, _, maxX, maxY, _ := item.Rect()
|
|
||||||
tr.tr.Remove([2]float64{minX, minY}, [2]float64{maxX, maxY}, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search finds all items in bounding box.
|
|
||||||
func (tr *RTree) Search(minX, minY, minZ, maxX, maxY, maxZ float64, iterator func(data interface{}) bool) {
|
|
||||||
// start := time.Now()
|
|
||||||
// var count int
|
|
||||||
tr.tr.Search([2]float64{minX, minY}, [2]float64{maxX, maxY}, func(data interface{}) bool {
|
|
||||||
// count++
|
|
||||||
return iterator(data)
|
|
||||||
})
|
|
||||||
// dur := time.Since(start)
|
|
||||||
// fmt.Printf("%s %d\n", dur, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count return the number of items in rtree.
|
|
||||||
func (tr *RTree) Count() int {
|
|
||||||
return tr.tr.Count()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAll removes all items from rtree.
|
|
||||||
func (tr *RTree) RemoveAll() {
|
|
||||||
tr.tr = rtreebase.New()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bounds returns the bounds of the R-tree
|
|
||||||
func (tr *RTree) Bounds() (minX, minY, maxX, maxY float64) {
|
|
||||||
min, max := tr.tr.Bounds()
|
|
||||||
return min[0], min[1], max[0], max[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// NearestNeighbors gets the closest Spatials to the Point.
|
|
||||||
func (tr *RTree) NearestNeighbors(x, y float64, iter func(item interface{}, dist float64) bool) bool {
|
|
||||||
return tr.tr.KNN([2]float64{x, y}, [2]float64{x, y}, true, func(item interface{}, dist float64) bool {
|
|
||||||
return iter(item, dist)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
package rtree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func randf(min, max float64) float64 {
|
|
||||||
return rand.Float64()*(max-min) + min
|
|
||||||
}
|
|
||||||
|
|
||||||
func randMinMax() (min, max []float64) {
|
|
||||||
minX, maxX := randf(-180, 180), randf(-180, 180)
|
|
||||||
minY, maxY := randf(-90, 90), randf(-90, 90)
|
|
||||||
minZ, maxZ := randf(0, 1000), randf(0, 1000)
|
|
||||||
min4, max4 := randf(0, 1000), randf(0, 1000)
|
|
||||||
if maxX < minX {
|
|
||||||
minX, maxX = maxX, minX
|
|
||||||
}
|
|
||||||
if maxY < minY {
|
|
||||||
minY, maxY = maxY, minY
|
|
||||||
}
|
|
||||||
if maxZ < minZ {
|
|
||||||
minZ, maxZ = maxZ, minZ
|
|
||||||
}
|
|
||||||
if max4 < min4 {
|
|
||||||
min4, max4 = max4, min4
|
|
||||||
}
|
|
||||||
return []float64{minX, minY, minZ, min4}, []float64{maxX, maxY, maxZ, max4}
|
|
||||||
}
|
|
||||||
|
|
||||||
func wp(min, max []float64) *Rect {
|
|
||||||
return &Rect{
|
|
||||||
MinX: min[0],
|
|
||||||
MinY: min[1],
|
|
||||||
MaxX: max[0],
|
|
||||||
MaxY: max[1],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func wpp(x, y, z float64) *Rect {
|
|
||||||
return &Rect{
|
|
||||||
x, y, z,
|
|
||||||
x, y, z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestA(t *testing.T) {
|
|
||||||
tr := New()
|
|
||||||
item1 := wp([]float64{10, 10, 10, 10}, []float64{20, 20, 20, 20})
|
|
||||||
item2 := wp([]float64{5, 5, 5, 5}, []float64{25, 25, 25, 25})
|
|
||||||
tr.Insert(item1)
|
|
||||||
tr.Insert(item2)
|
|
||||||
var itemA Item
|
|
||||||
tr.Search(21, 20, 0, 25, 25, 0, func(item interface{}) bool {
|
|
||||||
itemA = item.(Item)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if tr.Count() != 2 {
|
|
||||||
t.Fatalf("tr.Count() == %d, expect 2", tr.Count())
|
|
||||||
}
|
|
||||||
if itemA != item2 {
|
|
||||||
t.Fatalf("itemA == %v, expect %v", itemA, item2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMemory(t *testing.T) {
|
|
||||||
rand.Seed(0)
|
|
||||||
tr := New()
|
|
||||||
for i := 0; i < 100000; i++ {
|
|
||||||
min, max := randMinMax()
|
|
||||||
tr.Insert(wp(min, max))
|
|
||||||
}
|
|
||||||
runtime.GC()
|
|
||||||
var m runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&m)
|
|
||||||
println(int(m.HeapAlloc)/tr.Count(), "bytes/rect")
|
|
||||||
}
|
|
||||||
func TestBounds(t *testing.T) {
|
|
||||||
tr := New()
|
|
||||||
tr.Insert(wpp(10, 10, 0))
|
|
||||||
tr.Insert(wpp(10, 20, 0))
|
|
||||||
tr.Insert(wpp(10, 30, 0))
|
|
||||||
tr.Insert(wpp(20, 10, 0))
|
|
||||||
tr.Insert(wpp(30, 10, 0))
|
|
||||||
minX, minY, maxX, maxY := tr.Bounds()
|
|
||||||
if minX != 10 || minY != 10 || maxX != 30 || maxY != 30 {
|
|
||||||
t.Fatalf("expected 10,10 30,30, got %v,%v %v,%v\n", minX, minY, maxX, maxY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestKNN(t *testing.T) {
|
|
||||||
x, y := 20., 20.
|
|
||||||
tr := New()
|
|
||||||
tr.Insert(wpp(5, 5, 0))
|
|
||||||
tr.Insert(wpp(19, 19, 0))
|
|
||||||
tr.Insert(wpp(12, 19, 0))
|
|
||||||
tr.Insert(wpp(-5, 5, 0))
|
|
||||||
tr.Insert(wpp(33, 21, 0))
|
|
||||||
var items []Item
|
|
||||||
tr.NearestNeighbors(x, y, func(item interface{}, dist float64) bool {
|
|
||||||
items = append(items, item.(Item))
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
var res string
|
|
||||||
for i, item := range items {
|
|
||||||
ix, iy, _, _, _, _ := item.Rect()
|
|
||||||
res += fmt.Sprintf("%d:%v,%v\n", i, ix, iy)
|
|
||||||
}
|
|
||||||
if res != "0:19,19\n1:12,19\n2:33,21\n3:5,5\n4:-5,5\n" {
|
|
||||||
t.Fatal("invalid response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkInsert(b *testing.B) {
|
|
||||||
var rects []*Rect
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
rects = append(rects, wp(randMinMax()))
|
|
||||||
}
|
|
||||||
rand.Seed(0)
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
tr := New()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
tr.Insert(rects[i])
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
package rtreebase
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image/color"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tidwall/pinhole"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SavePNG draws and saves an image of the R-tree
|
|
||||||
func (tr *RTree) SavePNG(path string, width, height int, scale, rotateY float64, showNodes bool, withGIF bool, printer io.Writer) error {
|
|
||||||
return tr.savePNG2D(path, width, height, scale, rotateY, showNodes, withGIF, printer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tr *RTree) savePNG2D(path string, width, height int, scale, rotateY float64, showNodes bool, withGIF bool, printer io.Writer) error {
|
|
||||||
p := pinhole.New()
|
|
||||||
tr.Traverse(func(min, max [D]float64, level int, item interface{}) bool {
|
|
||||||
p.Begin()
|
|
||||||
if level > 0 && showNodes {
|
|
||||||
p.DrawCube(min[0], min[1], 0, max[0], max[1], 0)
|
|
||||||
switch level {
|
|
||||||
default:
|
|
||||||
p.Colorize(color.RGBA{64, 64, 64, 128})
|
|
||||||
case 1:
|
|
||||||
p.Colorize(color.RGBA{32, 64, 32, 64})
|
|
||||||
case 2:
|
|
||||||
p.Colorize(color.RGBA{48, 48, 96, 96})
|
|
||||||
case 3:
|
|
||||||
p.Colorize(color.RGBA{96, 128, 128, 128})
|
|
||||||
case 4:
|
|
||||||
p.Colorize(color.RGBA{128, 128, 196, 196})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.DrawDot(min[0], min[1], 0, 0.05)
|
|
||||||
p.Colorize(color.White)
|
|
||||||
}
|
|
||||||
p.End()
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
p.Scale(scale, scale, scale)
|
|
||||||
p.Rotate(0, rotateY, 0)
|
|
||||||
// render the paths in an image
|
|
||||||
opts := *pinhole.DefaultImageOptions
|
|
||||||
opts.LineWidth = 0.025
|
|
||||||
opts.BGColor = color.Black
|
|
||||||
if err := p.SavePNG(path, width, height, &opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if printer != nil {
|
|
||||||
fmt.Fprintf(printer, "wrote %s\n", path)
|
|
||||||
}
|
|
||||||
if withGIF {
|
|
||||||
if err := createGIF(p, width, height, path, &opts, printer); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func createGIF(p *pinhole.Pinhole, width, height int, path string, opts *pinhole.ImageOptions, printer io.Writer) error {
|
|
||||||
if err := os.MkdirAll("frames", 0700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//var palette = palette.WebSafe
|
|
||||||
//outGif := &gif.GIF{}
|
|
||||||
for i := 0; i < 120; i++ {
|
|
||||||
p.Rotate(0, math.Pi*2/120.0, 0)
|
|
||||||
if err := p.SavePNG(fmt.Sprintf("frames/%d.png", i), width, height, opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//inGif := image.NewPaletted(inPng.Bounds(), palette)
|
|
||||||
//draw.Draw(inGif, inPng.Bounds(), inPng, image.Point{}, draw.Src)
|
|
||||||
//outGif.Image = append(outGif.Image, inGif)
|
|
||||||
//outGif.Delay = append(outGif.Delay, 0)
|
|
||||||
if printer != nil {
|
|
||||||
fmt.Fprintf(printer, "wrote frame %d/%d\n", i, 120)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(path, ".png") {
|
|
||||||
path = path[:len(path)-4] + ".gif"
|
|
||||||
}
|
|
||||||
_, err := exec.Command("ffmpeg", "-y", "-i", "frames/%d.png", path).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
//ffmpeg -i frames/%d.png test.gif
|
|
||||||
//f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
|
|
||||||
//if err != nil {
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
//defer f.Close()
|
|
||||||
//if err := gif.EncodeAll(f, outGif); err != nil {
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
if printer != nil {
|
|
||||||
fmt.Fprintf(printer, "wrote %s\n", path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user