
The current KNN implementation has two areas that can be improved: - The current behavior is somewhat incorrect. When performing a kNN query, the current code fetches k items from the index, and then sorts these items according to Haversine distance. The problem with this approach is that since the items fetched from the index are ordered by a Euclidean metric, there is no guarantee that item k + 1 is not closer than item k in great circle distance, and hence incorrect results can be returned when closer items beyond k exist. - The secondary sort is a performance killer. This requires buffering all k items (again...they were already run through a priority queue in) the index, and then a sort. Since the items are mostly sorted, and Go's sort implementation is a quickSort this is the worst case for the sort algorithm. Both of these can be fixed by applying a proper distance metric in the index nearby operation. In addition, this cleans up the code considerably, removing a number of special cases that applied only to NEARBY operations. This change implements a geodetic distance metric that ensures that the order from the index is correct, eliminating the need for the secondary sort and special filtering cases in the ScanWriter code.
729 lines
20 KiB
Go
729 lines
20 KiB
Go
package collection
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tidwall/geojson"
|
|
"github.com/tidwall/geojson/geometry"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func PO(x, y float64) *geojson.Point {
|
|
return geojson.NewPoint(geometry.Point{X: x, Y: y})
|
|
}
|
|
|
|
func init() {
|
|
seed := time.Now().UnixNano()
|
|
println(seed)
|
|
rand.Seed(seed)
|
|
}
|
|
|
|
func expect(t testing.TB, expect bool) {
|
|
t.Helper()
|
|
if !expect {
|
|
t.Fatal("not what you expected")
|
|
}
|
|
}
|
|
|
|
func bounds(c *Collection) geometry.Rect {
|
|
minX, minY, maxX, maxY := c.Bounds()
|
|
return geometry.Rect{
|
|
Min: geometry.Point{X: minX, Y: minY},
|
|
Max: geometry.Point{X: maxX, Y: maxY},
|
|
}
|
|
}
|
|
|
|
func TestCollectionNewCollection(t *testing.T) {
|
|
const numItems = 10000
|
|
objs := make(map[string]geojson.Object)
|
|
c := New()
|
|
for i := 0; i < numItems; i++ {
|
|
id := strconv.FormatInt(int64(i), 10)
|
|
var obj geojson.Object
|
|
obj = PO(rand.Float64()*360-180, rand.Float64()*180-90)
|
|
objs[id] = obj
|
|
c.Set(id, obj, nil, nil)
|
|
}
|
|
count := 0
|
|
bbox := geometry.Rect{
|
|
Min: geometry.Point{X: -180, Y: -90},
|
|
Max: geometry.Point{X: 180, Y: 90},
|
|
}
|
|
c.geoSearch(bbox, func(id string, obj geojson.Object, field []float64) bool {
|
|
count++
|
|
return true
|
|
})
|
|
if count != len(objs) {
|
|
t.Fatalf("count = %d, expect %d", count, len(objs))
|
|
}
|
|
count = c.Count()
|
|
if count != len(objs) {
|
|
t.Fatalf("c.Count() = %d, expect %d", count, len(objs))
|
|
}
|
|
testCollectionVerifyContents(t, c, objs)
|
|
}
|
|
|
|
func TestCollectionSet(t *testing.T) {
|
|
t.Run("AddString", func(t *testing.T) {
|
|
c := New()
|
|
str1 := String("hello")
|
|
oldObject, oldFields, newFields := c.Set("str", str1, nil, nil)
|
|
expect(t, oldObject == nil)
|
|
expect(t, len(oldFields) == 0)
|
|
expect(t, len(newFields) == 0)
|
|
})
|
|
t.Run("UpdateString", func(t *testing.T) {
|
|
c := New()
|
|
str1 := String("hello")
|
|
str2 := String("world")
|
|
oldObject, oldFields, newFields := c.Set("str", str1, nil, nil)
|
|
expect(t, oldObject == nil)
|
|
expect(t, len(oldFields) == 0)
|
|
expect(t, len(newFields) == 0)
|
|
oldObject, oldFields, newFields = c.Set("str", str2, nil, nil)
|
|
expect(t, oldObject == str1)
|
|
expect(t, len(oldFields) == 0)
|
|
expect(t, len(newFields) == 0)
|
|
})
|
|
t.Run("AddPoint", func(t *testing.T) {
|
|
c := New()
|
|
point1 := PO(-112.1, 33.1)
|
|
oldObject, oldFields, newFields := c.Set("point", point1, nil, nil)
|
|
expect(t, oldObject == nil)
|
|
expect(t, len(oldFields) == 0)
|
|
expect(t, len(newFields) == 0)
|
|
})
|
|
t.Run("UpdatePoint", func(t *testing.T) {
|
|
c := New()
|
|
point1 := PO(-112.1, 33.1)
|
|
point2 := PO(-112.2, 33.2)
|
|
oldObject, oldFields, newFields := c.Set("point", point1, nil, nil)
|
|
expect(t, oldObject == nil)
|
|
expect(t, len(oldFields) == 0)
|
|
expect(t, len(newFields) == 0)
|
|
oldObject, oldFields, newFields = c.Set("point", point2, nil, nil)
|
|
expect(t, oldObject == point1)
|
|
expect(t, len(oldFields) == 0)
|
|
expect(t, len(newFields) == 0)
|
|
})
|
|
t.Run("Fields", func(t *testing.T) {
|
|
c := New()
|
|
str1 := String("hello")
|
|
fNames := []string{"a", "b", "c"}
|
|
fValues := []float64{1, 2, 3}
|
|
oldObj, oldFlds, newFlds := c.Set("str", str1, fNames, fValues)
|
|
expect(t, oldObj == nil)
|
|
expect(t, len(oldFlds) == 0)
|
|
expect(t, reflect.DeepEqual(newFlds, fValues))
|
|
str2 := String("hello")
|
|
fNames = []string{"d", "e", "f"}
|
|
fValues = []float64{4, 5, 6}
|
|
oldObj, oldFlds, newFlds = c.Set("str", str2, fNames, fValues)
|
|
expect(t, oldObj == str1)
|
|
expect(t, reflect.DeepEqual(oldFlds, []float64{1, 2, 3}))
|
|
expect(t, reflect.DeepEqual(newFlds, []float64{1, 2, 3, 4, 5, 6}))
|
|
fValues = []float64{7, 8, 9, 10, 11, 12}
|
|
oldObj, oldFlds, newFlds = c.Set("str", str1, nil, fValues)
|
|
expect(t, oldObj == str2)
|
|
expect(t, reflect.DeepEqual(oldFlds, []float64{1, 2, 3, 4, 5, 6}))
|
|
expect(t, reflect.DeepEqual(newFlds, []float64{7, 8, 9, 10, 11, 12}))
|
|
})
|
|
t.Run("Delete", func(t *testing.T) {
|
|
c := New()
|
|
|
|
c.Set("1", String("1"), nil, nil)
|
|
c.Set("2", String("2"), nil, nil)
|
|
c.Set("3", PO(1, 2), nil, nil)
|
|
|
|
expect(t, c.Count() == 3)
|
|
expect(t, c.StringCount() == 2)
|
|
expect(t, c.PointCount() == 1)
|
|
expect(t, bounds(c) == geometry.Rect{
|
|
Min: geometry.Point{X: 1, Y: 2},
|
|
Max: geometry.Point{X: 1, Y: 2}})
|
|
var v geojson.Object
|
|
var ok bool
|
|
var flds []float64
|
|
var updated bool
|
|
var updateCount int
|
|
|
|
v, _, ok = c.Delete("2")
|
|
expect(t, v.String() == "2")
|
|
expect(t, ok)
|
|
expect(t, c.Count() == 2)
|
|
expect(t, c.StringCount() == 1)
|
|
expect(t, c.PointCount() == 1)
|
|
|
|
v, _, ok = c.Delete("1")
|
|
expect(t, v.String() == "1")
|
|
expect(t, ok)
|
|
expect(t, c.Count() == 1)
|
|
expect(t, c.StringCount() == 0)
|
|
expect(t, c.PointCount() == 1)
|
|
|
|
expect(t, len(c.FieldMap()) == 0)
|
|
|
|
v, flds, updated, ok = c.SetField("3", "hello", 123)
|
|
expect(t, ok)
|
|
expect(t, reflect.DeepEqual(flds, []float64{123}))
|
|
expect(t, updated)
|
|
expect(t, c.FieldMap()["hello"] == 0)
|
|
|
|
v, flds, updated, ok = c.SetField("3", "hello", 1234)
|
|
expect(t, ok)
|
|
expect(t, reflect.DeepEqual(flds, []float64{1234}))
|
|
expect(t, updated)
|
|
|
|
v, flds, updated, ok = c.SetField("3", "hello", 1234)
|
|
expect(t, ok)
|
|
expect(t, reflect.DeepEqual(flds, []float64{1234}))
|
|
expect(t, !updated)
|
|
|
|
v, flds, updateCount, ok = c.SetFields("3",
|
|
[]string{"planet", "world"}, []float64{55, 66})
|
|
expect(t, ok)
|
|
expect(t, reflect.DeepEqual(flds, []float64{1234, 55, 66}))
|
|
expect(t, updateCount == 2)
|
|
expect(t, c.FieldMap()["hello"] == 0)
|
|
expect(t, c.FieldMap()["planet"] == 1)
|
|
expect(t, c.FieldMap()["world"] == 2)
|
|
|
|
v, _, ok = c.Delete("3")
|
|
expect(t, v.String() == `{"type":"Point","coordinates":[1,2]}`)
|
|
expect(t, ok)
|
|
expect(t, c.Count() == 0)
|
|
expect(t, c.StringCount() == 0)
|
|
expect(t, c.PointCount() == 0)
|
|
v, _, ok = c.Delete("3")
|
|
expect(t, v == nil)
|
|
expect(t, !ok)
|
|
expect(t, c.Count() == 0)
|
|
expect(t, bounds(c) == geometry.Rect{})
|
|
v, _, ok = c.Get("3")
|
|
expect(t, v == nil)
|
|
expect(t, !ok)
|
|
_, _, _, ok = c.SetField("3", "hello", 123)
|
|
expect(t, !ok)
|
|
_, _, _, ok = c.SetFields("3", []string{"hello"}, []float64{123})
|
|
expect(t, !ok)
|
|
expect(t, c.TotalWeight() == 0)
|
|
expect(t, c.FieldMap()["hello"] == 0)
|
|
expect(t, c.FieldMap()["planet"] == 1)
|
|
expect(t, c.FieldMap()["world"] == 2)
|
|
expect(t, reflect.DeepEqual(
|
|
c.FieldArr(), []string{"hello", "planet", "world"}),
|
|
)
|
|
})
|
|
}
|
|
|
|
func TestCollectionScan(t *testing.T) {
|
|
N := 256
|
|
c := New()
|
|
for _, i := range rand.Perm(N) {
|
|
id := fmt.Sprintf("%04d", i)
|
|
c.Set(id, String(id), []string{"ex"}, []float64{float64(i)})
|
|
}
|
|
var n int
|
|
var prevID string
|
|
c.Scan(false, nil, nil, func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, id > prevID)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[0])))
|
|
n++
|
|
prevID = id
|
|
return true
|
|
})
|
|
expect(t, n == c.Count())
|
|
n = 0
|
|
c.Scan(true, nil, nil, func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, id < prevID)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[0])))
|
|
n++
|
|
prevID = id
|
|
return true
|
|
})
|
|
expect(t, n == c.Count())
|
|
|
|
n = 0
|
|
c.ScanRange("0060", "0070", false, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, id > prevID)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[0])))
|
|
n++
|
|
prevID = id
|
|
return true
|
|
})
|
|
expect(t, n == 10)
|
|
|
|
n = 0
|
|
c.ScanRange("0070", "0060", true, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, id < prevID)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[0])))
|
|
n++
|
|
prevID = id
|
|
return true
|
|
})
|
|
expect(t, n == 10)
|
|
|
|
n = 0
|
|
c.ScanGreaterOrEqual("0070", true, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, id < prevID)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[0])))
|
|
n++
|
|
prevID = id
|
|
return true
|
|
})
|
|
expect(t, n == 71)
|
|
|
|
n = 0
|
|
c.ScanGreaterOrEqual("0070", false, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, id > prevID)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[0])))
|
|
n++
|
|
prevID = id
|
|
return true
|
|
})
|
|
expect(t, n == c.Count()-70)
|
|
|
|
}
|
|
|
|
func TestCollectionSearch(t *testing.T) {
|
|
N := 256
|
|
c := New()
|
|
for i, j := range rand.Perm(N) {
|
|
id := fmt.Sprintf("%04d", j)
|
|
ex := fmt.Sprintf("%04d", i)
|
|
c.Set(id, String(ex), []string{"i", "j"},
|
|
[]float64{float64(i), float64(j)})
|
|
}
|
|
var n int
|
|
var prevValue string
|
|
c.SearchValues(false, nil, nil, func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, obj.String() > prevValue)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[1])))
|
|
n++
|
|
prevValue = obj.String()
|
|
return true
|
|
})
|
|
expect(t, n == c.Count())
|
|
n = 0
|
|
c.SearchValues(true, nil, nil, func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, obj.String() < prevValue)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[1])))
|
|
n++
|
|
prevValue = obj.String()
|
|
return true
|
|
})
|
|
expect(t, n == c.Count())
|
|
|
|
n = 0
|
|
c.SearchValuesRange("0060", "0070", false, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, obj.String() > prevValue)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[1])))
|
|
n++
|
|
prevValue = obj.String()
|
|
return true
|
|
})
|
|
expect(t, n == 10)
|
|
|
|
n = 0
|
|
c.SearchValuesRange("0070", "0060", true, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
if n > 0 {
|
|
expect(t, obj.String() < prevValue)
|
|
}
|
|
expect(t, id == fmt.Sprintf("%04d", int(fields[1])))
|
|
n++
|
|
prevValue = obj.String()
|
|
return true
|
|
})
|
|
expect(t, n == 10)
|
|
}
|
|
|
|
func TestCollectionWeight(t *testing.T) {
|
|
c := New()
|
|
c.Set("1", String("1"), nil, nil)
|
|
expect(t, c.TotalWeight() > 0)
|
|
c.Delete("1")
|
|
expect(t, c.TotalWeight() == 0)
|
|
c.Set("1", String("1"),
|
|
[]string{"a", "b", "c"},
|
|
[]float64{1, 2, 3},
|
|
)
|
|
expect(t, c.TotalWeight() > 0)
|
|
c.Delete("1")
|
|
expect(t, c.TotalWeight() == 0)
|
|
c.Set("1", String("1"),
|
|
[]string{"a", "b", "c"},
|
|
[]float64{1, 2, 3},
|
|
)
|
|
c.Set("2", String("2"),
|
|
[]string{"d", "e", "f"},
|
|
[]float64{4, 5, 6},
|
|
)
|
|
c.Set("1", String("1"),
|
|
[]string{"d", "e", "f"},
|
|
[]float64{4, 5, 6},
|
|
)
|
|
c.Delete("1")
|
|
c.Delete("2")
|
|
expect(t, c.TotalWeight() == 0)
|
|
}
|
|
|
|
func TestSpatialSearch(t *testing.T) {
|
|
json := `
|
|
{"type":"FeatureCollection","features":[
|
|
{"type":"Feature","id":"p1","properties":{"marker-color":"#962d28","stroke":"#962d28","fill":"#962d28"},"geometry":{"type":"Point","coordinates":[-71.4743041992187,42.51867517417283]}},
|
|
{"type":"Feature","id":"p2","properties":{"marker-color":"#962d28","stroke":"#962d28","fill":"#962d28"},"geometry":{"type":"Point","coordinates":[-71.4056396484375,42.50197174319114]}},
|
|
{"type":"Feature","id":"p3","properties":{"marker-color":"#962d28","stroke":"#962d28","fill":"#962d28"},"geometry":{"type":"Point","coordinates":[-71.4619445800781,42.49437779897246]}},
|
|
{"type":"Feature","id":"p4","properties":{"marker-color":"#962d28","stroke":"#962d28","fill":"#962d28"},"geometry":{"type":"Point","coordinates":[-71.4337921142578,42.53891577257117]}},
|
|
{"type":"Feature","id":"r1","properties":{"marker-color":"#962d28","stroke":"#962d28","fill":"#962d28"},"geometry":{"type":"Polygon","coordinates":[[[-71.4279556274414,42.48804880765346],[-71.37439727783203,42.48804880765346],[-71.37439727783203,42.52322988064187],[-71.4279556274414,42.52322988064187],[-71.4279556274414,42.48804880765346]]]}},
|
|
{"type":"Feature","id":"r2","properties":{"marker-color":"#962d28","stroke":"#962d28","fill":"#962d28"},"geometry":{"type":"Polygon","coordinates":[[[-71.4825439453125,42.53588010092859],[-71.45027160644531,42.53588010092859],[-71.45027160644531,42.55839115400447],[-71.4825439453125,42.55839115400447],[-71.4825439453125,42.53588010092859]]]}},
|
|
{"type":"Feature","id":"r3","properties":{"marker-color":"#962d28","stroke":"#962d28","fill":"#962d28"},"geometry":{"type":"Polygon","coordinates": [[[-71.4111328125,42.53512115995963],[-71.3833236694336,42.53512115995963],[-71.3833236694336,42.54953946116446],[-71.4111328125,42.54953946116446],[-71.4111328125,42.53512115995963]]]}},
|
|
{"type":"Feature","id":"q1","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-71.55258178710938,42.51361399979923],[-71.42074584960938,42.51361399979923],[-71.42074584960938,42.59100512331456],[-71.55258178710938,42.59100512331456],[-71.55258178710938,42.51361399979923]]]}},
|
|
{"type":"Feature","id":"q2","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-71.52992248535156,42.48121277771616],[-71.36375427246092,42.48121277771616],[-71.36375427246092,42.57786045892046],[-71.52992248535156,42.57786045892046],[-71.52992248535156,42.48121277771616]]]}},
|
|
{"type":"Feature","id":"q3","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-71.49490356445312,42.56673588590953],[-71.52236938476562,42.47462922809497],[-71.42898559570312,42.464499337722344],[-71.43241882324219,42.522217752342236],[-71.37954711914061,42.56420729713456],[-71.49490356445312,42.56673588590953]]]}},
|
|
{"type":"Feature","id":"q4","properties":{},"geometry":{"type":"Point","coordinates": [-71.46366119384766,42.54043355305221]}}
|
|
]}
|
|
`
|
|
p1, _ := geojson.Parse(gjson.Get(json, `features.#[id=="p1"]`).Raw, nil)
|
|
p2, _ := geojson.Parse(gjson.Get(json, `features.#[id=="p2"]`).Raw, nil)
|
|
p3, _ := geojson.Parse(gjson.Get(json, `features.#[id=="p3"]`).Raw, nil)
|
|
p4, _ := geojson.Parse(gjson.Get(json, `features.#[id=="p4"]`).Raw, nil)
|
|
r1, _ := geojson.Parse(gjson.Get(json, `features.#[id=="r1"]`).Raw, nil)
|
|
r2, _ := geojson.Parse(gjson.Get(json, `features.#[id=="r2"]`).Raw, nil)
|
|
r3, _ := geojson.Parse(gjson.Get(json, `features.#[id=="r3"]`).Raw, nil)
|
|
q1, _ := geojson.Parse(gjson.Get(json, `features.#[id=="q1"]`).Raw, nil)
|
|
q2, _ := geojson.Parse(gjson.Get(json, `features.#[id=="q2"]`).Raw, nil)
|
|
q3, _ := geojson.Parse(gjson.Get(json, `features.#[id=="q3"]`).Raw, nil)
|
|
q4, _ := geojson.Parse(gjson.Get(json, `features.#[id=="q4"]`).Raw, nil)
|
|
|
|
c := New()
|
|
c.Set("p1", p1, nil, nil)
|
|
c.Set("p2", p2, nil, nil)
|
|
c.Set("p3", p3, nil, nil)
|
|
c.Set("p4", p4, nil, nil)
|
|
c.Set("r1", r1, nil, nil)
|
|
c.Set("r2", r2, nil, nil)
|
|
c.Set("r3", r3, nil, nil)
|
|
|
|
var n int
|
|
|
|
n = 0
|
|
c.Within(q1, 0, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 3)
|
|
|
|
n = 0
|
|
c.Within(q2, 0, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 7)
|
|
|
|
n = 0
|
|
c.Within(q3, 0, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 4)
|
|
|
|
n = 0
|
|
c.Intersects(q1, 0, nil, nil,
|
|
func(_ string, _ geojson.Object, _ []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 4)
|
|
|
|
n = 0
|
|
c.Intersects(q2, 0, nil, nil,
|
|
func(_ string, _ geojson.Object, _ []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 7)
|
|
|
|
n = 0
|
|
c.Intersects(q3, 0, nil, nil,
|
|
func(_ string, _ geojson.Object, _ []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 5)
|
|
|
|
n = 0
|
|
c.Intersects(q3, 0, nil, nil,
|
|
func(_ string, _ geojson.Object, _ []float64) bool {
|
|
n++
|
|
return n <= 1
|
|
},
|
|
)
|
|
expect(t, n == 2)
|
|
|
|
var items []geojson.Object
|
|
exitems := []geojson.Object{
|
|
r2, p4, p1, r1, r3, p3, p2,
|
|
}
|
|
|
|
lastDist := float64(-1)
|
|
distsMonotonic := true
|
|
c.Nearby(q4, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64, dist float64) bool {
|
|
if dist < lastDist {
|
|
distsMonotonic = false
|
|
}
|
|
items = append(items, obj)
|
|
return true
|
|
},
|
|
)
|
|
expect(t, len(items) == 7)
|
|
expect(t, distsMonotonic)
|
|
expect(t, reflect.DeepEqual(items, exitems))
|
|
}
|
|
|
|
func TestCollectionSparse(t *testing.T) {
|
|
rect := geojson.NewRect(geometry.Rect{
|
|
Min: geometry.Point{X: -71.598930, Y: 42.4586739},
|
|
Max: geometry.Point{X: -71.37302, Y: 42.607937},
|
|
})
|
|
N := 10000
|
|
c := New()
|
|
r := rect.Rect()
|
|
for i := 0; i < N; i++ {
|
|
x := (r.Max.X-r.Min.X)*rand.Float64() + r.Min.X
|
|
y := (r.Max.Y-r.Min.Y)*rand.Float64() + r.Min.Y
|
|
point := PO(x, y)
|
|
c.Set(fmt.Sprintf("%d", i), point, nil, nil)
|
|
}
|
|
var n int
|
|
n = 0
|
|
c.Within(rect, 1, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 4)
|
|
|
|
n = 0
|
|
c.Within(rect, 2, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 16)
|
|
|
|
n = 0
|
|
c.Within(rect, 3, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 64)
|
|
|
|
n = 0
|
|
c.Within(rect, 3, nil, nil,
|
|
func(id string, obj geojson.Object, fields []float64) bool {
|
|
n++
|
|
return n <= 30
|
|
},
|
|
)
|
|
expect(t, n == 31)
|
|
|
|
n = 0
|
|
c.Intersects(rect, 3, nil, nil,
|
|
func(id string, _ geojson.Object, _ []float64) bool {
|
|
n++
|
|
return true
|
|
},
|
|
)
|
|
expect(t, n == 64)
|
|
|
|
n = 0
|
|
c.Intersects(rect, 3, nil, nil,
|
|
func(id string, _ geojson.Object, _ []float64) bool {
|
|
n++
|
|
return n <= 30
|
|
},
|
|
)
|
|
expect(t, n == 31)
|
|
|
|
}
|
|
|
|
func testCollectionVerifyContents(t *testing.T, c *Collection, objs map[string]geojson.Object) {
|
|
for id, o2 := range objs {
|
|
o1, _, ok := c.Get(id)
|
|
if !ok {
|
|
t.Fatalf("ok[%s] = false, expect true", id)
|
|
}
|
|
j1 := string(o1.AppendJSON(nil))
|
|
j2 := string(o2.AppendJSON(nil))
|
|
if j1 != j2 {
|
|
t.Fatalf("j1 == %s, expect %s", j1, j2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestManyCollections(t *testing.T) {
|
|
colsM := make(map[string]*Collection)
|
|
cols := 100
|
|
objs := 1000
|
|
k := 0
|
|
for i := 0; i < cols; i++ {
|
|
key := strconv.FormatInt(int64(i), 10)
|
|
for j := 0; j < objs; j++ {
|
|
id := strconv.FormatInt(int64(j), 10)
|
|
p := geometry.Point{
|
|
X: rand.Float64()*360 - 180,
|
|
Y: rand.Float64()*180 - 90,
|
|
}
|
|
obj := geojson.Object(PO(p.X, p.Y))
|
|
col, ok := colsM[key]
|
|
if !ok {
|
|
col = New()
|
|
colsM[key] = col
|
|
}
|
|
col.Set(id, obj, nil, nil)
|
|
k++
|
|
}
|
|
}
|
|
|
|
col := colsM["13"]
|
|
//println(col.Count())
|
|
bbox := geometry.Rect{
|
|
Min: geometry.Point{X: -180, Y: 30},
|
|
Max: geometry.Point{X: 34, Y: 100},
|
|
}
|
|
col.geoSearch(bbox, func(id string, obj geojson.Object, fields []float64) bool {
|
|
//println(id)
|
|
return true
|
|
})
|
|
}
|
|
|
|
type testPointItem struct {
|
|
id string
|
|
object geojson.Object
|
|
}
|
|
|
|
func BenchmarkInsert(t *testing.B) {
|
|
rand.Seed(time.Now().UnixNano())
|
|
items := make([]testPointItem, t.N)
|
|
for i := 0; i < t.N; i++ {
|
|
items[i] = testPointItem{
|
|
fmt.Sprintf("%d", i),
|
|
PO(rand.Float64()*360-180, rand.Float64()*180-90),
|
|
}
|
|
}
|
|
col := New()
|
|
t.ResetTimer()
|
|
for i := 0; i < t.N; i++ {
|
|
col.Set(items[i].id, items[i].object, nil, nil)
|
|
}
|
|
}
|
|
|
|
func BenchmarkReplace(t *testing.B) {
|
|
rand.Seed(time.Now().UnixNano())
|
|
items := make([]testPointItem, t.N)
|
|
for i := 0; i < t.N; i++ {
|
|
items[i] = testPointItem{
|
|
fmt.Sprintf("%d", i),
|
|
PO(rand.Float64()*360-180, rand.Float64()*180-90),
|
|
}
|
|
}
|
|
col := New()
|
|
for i := 0; i < t.N; i++ {
|
|
col.Set(items[i].id, items[i].object, nil, nil)
|
|
}
|
|
t.ResetTimer()
|
|
for _, i := range rand.Perm(t.N) {
|
|
o, _, _ := col.Set(items[i].id, items[i].object, nil, nil)
|
|
if o != items[i].object {
|
|
t.Fatal("shoot!")
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkGet(t *testing.B) {
|
|
rand.Seed(time.Now().UnixNano())
|
|
items := make([]testPointItem, t.N)
|
|
for i := 0; i < t.N; i++ {
|
|
items[i] = testPointItem{
|
|
fmt.Sprintf("%d", i),
|
|
PO(rand.Float64()*360-180, rand.Float64()*180-90),
|
|
}
|
|
}
|
|
col := New()
|
|
for i := 0; i < t.N; i++ {
|
|
col.Set(items[i].id, items[i].object, nil, nil)
|
|
}
|
|
t.ResetTimer()
|
|
for _, i := range rand.Perm(t.N) {
|
|
o, _, _ := col.Get(items[i].id)
|
|
if o != items[i].object {
|
|
t.Fatal("shoot!")
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkRemove(t *testing.B) {
|
|
rand.Seed(time.Now().UnixNano())
|
|
items := make([]testPointItem, t.N)
|
|
for i := 0; i < t.N; i++ {
|
|
items[i] = testPointItem{
|
|
fmt.Sprintf("%d", i),
|
|
PO(rand.Float64()*360-180, rand.Float64()*180-90),
|
|
}
|
|
}
|
|
col := New()
|
|
for i := 0; i < t.N; i++ {
|
|
col.Set(items[i].id, items[i].object, nil, nil)
|
|
}
|
|
t.ResetTimer()
|
|
for _, i := range rand.Perm(t.N) {
|
|
o, _, _ := col.Delete(items[i].id)
|
|
if o != items[i].object {
|
|
t.Fatal("shoot!")
|
|
}
|
|
}
|
|
}
|