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:
Josh Baker 2018-05-08 06:56:28 -07:00
parent db7d8972aa
commit 565f32cc5b
12 changed files with 112 additions and 723 deletions

View File

@ -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()
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) {
x, y, z, _, _, _ = i.Rect()
func (i *itemT) Point() (x, y float64) {
x, y, _, _ = i.Rect()
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 {
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)
if !iterator(iitm.id, iitm.object, c.getFieldValues(iitm.id)) {
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 {
return c.index.NearestNeighbors(lat, lon, func(item interface{}) bool {
return c.index.KNN(lon, lat, func(item interface{}) bool {
var iitm *itemT
iitm, ok := item.(*itemT)
if !ok {

View File

@ -1,205 +1,84 @@
package index
import (
"math"
"unsafe"
"github.com/tidwall/tile38/pkg/index/rtree"
rtree "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
type Index struct {
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
r *rtree.RTree
}
// New create a new index
func New() *Index {
return &Index{
r: rtree.New(),
mulm: make(map[interface{}]bool),
nr: make(map[*rtree.Rect]Item),
nrr: make(map[Item][]*rtree.Rect),
r: rtree.New(),
}
}
// 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
func (ix *Index) Insert(item Item) {
minX, minY, minZ, maxX, maxY, maxZ := item.Rect()
if minX == maxX && minY == maxY {
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
minX, minY, maxX, maxY := item.Rect()
ix.r.Insert([2]float64{minX, minY}, [2]float64{maxX, maxY}, item)
}
// Remove removed an item from the index
func (ix *Index) Remove(item Item) {
if nitems, ok := ix.nrr[item]; ok {
for _, nitem := range nitems {
ix.r.Remove(nitem)
delete(ix.nr, nitem)
}
delete(ix.nrr, item)
} else {
ix.r.Remove(item)
}
minX, minY, maxX, maxY := item.Rect()
ix.r.Remove([2]float64{minX, minY}, [2]float64{maxX, maxY}, item)
}
// Count counts all items in the index.
func (ix *Index) Count() int {
count := 0
ix.Search(-90, -180, 90, 180, math.Inf(-1), math.Inf(+1), func(_ interface{}) bool {
count++
return true
})
return count
return ix.r.Count()
}
// Bounds returns the minimum bounding rectangle of all items in the index.
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.
func (ix *Index) RemoveAll() {
ix.r.RemoveAll()
ix.r = rtree.New()
}
type UintptrInterface struct {
Type uintptr
Ptr uintptr
}
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))
})
func (ix *Index) KNN(x, y float64, iterator func(item interface{}) bool) bool {
return ix.r.KNN([2]float64{x, y}, [2]float64{x, y}, true,
func(item interface{}, dist float64) bool {
return iterator(item)
})
}
// 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,
) bool {
var keepon = true
var idm = make(map[interface{}]bool)
mins, maxs, _ := normRect(swLat, swLon, neLat, neLon)
// 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
return ix.r.Search([2]float64{minX, minY}, [2]float64{maxX, maxY},
func(item interface{}) bool {
return iterator(item)
})
}

View File

@ -8,48 +8,54 @@ import (
"time"
)
func init() {
seed := time.Now().UnixNano()
fmt.Printf("seed: %d\n", seed)
rand.Seed(seed)
}
func randf(min, max float64) float64 {
return rand.Float64()*(max-min) + min
}
func randPoint() (lat float64, lon float64) {
// intentionally go out of range.
return randf(-100, 100), randf(-190, 190)
}
func randRect() (swLat, swLon, neLat, neLon float64) {
swLat, swLon = randPoint()
// intentionally go out of range even more.
neLat = randf(swLat-10, swLat+10)
neLon = randf(swLon-10, swLon+10)
func randRect() (minX, minY, maxX, maxY float64) {
minX, minY = rand.Float64()*360-180, rand.Float64()*180-90
maxX, maxY = rand.Float64()*360-180, rand.Float64()*180-90
if minX > maxX {
minX, maxX = maxX, minX
}
if minY > maxY {
minY, maxY = maxY, minY
}
return
}
func wp(swLat, swLon, neLat, neLon float64) *FlexItem {
func wp(minX, minY, maxX, maxY float64) *FlexItem {
return &FlexItem{
MinX: swLon,
MinY: swLat,
MaxX: neLon,
MaxY: neLat,
MinX: minX,
MinY: minY,
MaxX: maxX,
MaxY: maxY,
}
}
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++ {
swLat, swLon, neLat, neLon := randRect()
tr.Insert(wp(swLat, swLon, neLat, neLon))
item := wp(randRect())
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)
count := 0
@ -60,17 +66,18 @@ func TestRandomInserts(t *testing.T) {
}
count = 0
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++
items = append(items, item.(Item))
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()
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++
return true
})
@ -79,7 +86,7 @@ func TestRandomInserts(t *testing.T) {
start = time.Now()
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++
return true
})
@ -87,28 +94,27 @@ func TestRandomInserts(t *testing.T) {
start = time.Now()
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++
return true
})
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/2, insrdur.String())
fmt.Printf("Randomly inserted %d rects in %s.\n", l, insrdur.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", count3, searchdur3.String())
tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
lat1, lon1, _, lat2, lon2, _ := item.(Item).Rect()
tr.Search(-10, 170, 20, 200, func(item interface{}) bool {
lat1, lon1, lat2, lon2 := item.(Item).Rect()
if lat1 == lat2 && lon1 == lon2 {
return false
}
return true
})
tr.Search(-10, 170, 20, 200, 0, 0, func(item interface{}) bool {
lat1, lon1, _, lat2, lon2, _ := item.(Item).Rect()
tr.Search(-10, 170, 20, 200, func(item interface{}) bool {
lat1, lon1, lat2, lon2 := item.(Item).Rect()
if lat1 != lat2 || lon1 != lon2 {
return false
}
@ -130,9 +136,9 @@ func TestRandomInserts(t *testing.T) {
t.Fatal("getQTreeItem(nil) should return nil")
}
*/
if tr.getRTreeItem(nil) != nil {
t.Fatal("getRTreeItem(nil) should return nil")
}
// if tr.getRTreeItem(nil) != nil {
// t.Fatal("getRTreeItem(nil) should return nil")
// }
}
func TestMemory(t *testing.T) {
@ -173,7 +179,7 @@ func TestInsertVarious(t *testing.T) {
t.Fatalf("count = %d, expect 1", count)
}
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 {
found = true
}

View File

@ -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
}
}

View File

@ -1,30 +1,7 @@
// Package rtreebase
// This package is a port of the fantastic RBush project by Vladimir Agafonkin.
// This package is adapted from the RBush project by Vladimir Agafonkin.
// 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"

View File

@ -1,4 +1,4 @@
package rtreebase
package rtree
import (
"fmt"

View File

@ -1,32 +1,11 @@
// Package rtreebase
// This package is a port of the fantastic RBush project by Vladimir Agafonkin.
// This package is adapted from the RBush project by Vladimir Agafonkin.
// 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 {
node *treeNode

View File

@ -1,32 +1,11 @@
// Package rtreebase
// This package is a port of the fantastic RBush project by Vladimir Agafonkin.
// This package is adapted from the RBush project by Vladimir Agafonkin.
// 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.
func (tr *RTree) Load(mins, maxs [][D]float64, items []interface{}) {

View File

@ -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)
})
}

View File

@ -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])
}
}

View File

@ -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
}