diff --git a/pkg/collection/collection.go b/pkg/collection/collection.go index 0b850ed0..8e8fb7f9 100644 --- a/pkg/collection/collection.go +++ b/pkg/collection/collection.go @@ -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 { diff --git a/pkg/index/index.go b/pkg/index/index.go index 549a618d..200d8618 100644 --- a/pkg/index/index.go +++ b/pkg/index/index.go @@ -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) + }) } diff --git a/pkg/index/index_test.go b/pkg/index/index_test.go index 5cfc9d64..fdd6c893 100644 --- a/pkg/index/index_test.go +++ b/pkg/index/index_test.go @@ -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 } diff --git a/pkg/index/norm.go b/pkg/index/norm.go deleted file mode 100644 index 0a68e364..00000000 --- a/pkg/index/norm.go +++ /dev/null @@ -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 - } -} diff --git a/pkg/index/rtreebase/.gitignore b/pkg/index/rtree/.gitignore similarity index 100% rename from pkg/index/rtreebase/.gitignore rename to pkg/index/rtree/.gitignore diff --git a/pkg/index/rtreebase/base.go b/pkg/index/rtree/base.go similarity index 92% rename from pkg/index/rtreebase/base.go rename to pkg/index/rtree/base.go index cfc70d71..de5d603f 100644 --- a/pkg/index/rtreebase/base.go +++ b/pkg/index/rtree/base.go @@ -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" diff --git a/pkg/index/rtreebase/base_test.go b/pkg/index/rtree/base_test.go similarity index 99% rename from pkg/index/rtreebase/base_test.go rename to pkg/index/rtree/base_test.go index 28320001..f020fca2 100644 --- a/pkg/index/rtreebase/base_test.go +++ b/pkg/index/rtree/base_test.go @@ -1,4 +1,4 @@ -package rtreebase +package rtree import ( "fmt" diff --git a/pkg/index/rtreebase/knn.go b/pkg/index/rtree/knn.go similarity index 60% rename from pkg/index/rtreebase/knn.go rename to pkg/index/rtree/knn.go index 615962d0..fa55a646 100644 --- a/pkg/index/rtreebase/knn.go +++ b/pkg/index/rtree/knn.go @@ -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 diff --git a/pkg/index/rtreebase/load.go b/pkg/index/rtree/load.go similarity index 66% rename from pkg/index/rtreebase/load.go rename to pkg/index/rtree/load.go index 1010f0a3..f5550c5c 100644 --- a/pkg/index/rtreebase/load.go +++ b/pkg/index/rtree/load.go @@ -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{}) { diff --git a/pkg/index/rtree/rtree.go b/pkg/index/rtree/rtree.go deleted file mode 100644 index 974bc537..00000000 --- a/pkg/index/rtree/rtree.go +++ /dev/null @@ -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) - }) -} diff --git a/pkg/index/rtree/rtree_test.go b/pkg/index/rtree/rtree_test.go deleted file mode 100644 index d8769e13..00000000 --- a/pkg/index/rtree/rtree_test.go +++ /dev/null @@ -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]) - } -} diff --git a/pkg/index/rtreebase/draw.go-bak b/pkg/index/rtreebase/draw.go-bak deleted file mode 100644 index 84e9c4a2..00000000 --- a/pkg/index/rtreebase/draw.go-bak +++ /dev/null @@ -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 -}