mirror of https://github.com/tidwall/tile38.git
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()
|
||||
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 {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 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"
|
|
@ -1,4 +1,4 @@
|
|||
package rtreebase
|
||||
package rtree
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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
|
|
@ -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{}) {
|
|
@ -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…
Reference in New Issue