2019-02-13 22:43:38 +03:00
|
|
|
package item
|
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"github.com/tidwall/btree"
|
|
|
|
"github.com/tidwall/geojson"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Item is a item for Tile38 collections
|
|
|
|
type Item struct {
|
2019-02-16 00:02:04 +03:00
|
|
|
head [2]uint32 // (1:isPoint,1:isPacked,30:fieldsByteLen),(32:idLen)
|
2019-02-15 22:51:26 +03:00
|
|
|
data unsafe.Pointer // pointer to raw block of bytes, fields+id
|
2019-02-15 01:53:46 +03:00
|
|
|
}
|
|
|
|
type objItem struct {
|
2019-02-15 22:51:26 +03:00
|
|
|
_ [2]uint32
|
2019-02-15 01:53:46 +03:00
|
|
|
_ unsafe.Pointer
|
|
|
|
obj geojson.Object
|
|
|
|
}
|
|
|
|
type pointItem struct {
|
2019-02-15 22:51:26 +03:00
|
|
|
_ [2]uint32
|
2019-02-15 01:53:46 +03:00
|
|
|
_ unsafe.Pointer
|
|
|
|
pt geojson.SimplePoint
|
2019-02-13 22:43:38 +03:00
|
|
|
}
|
|
|
|
|
2019-02-16 00:02:04 +03:00
|
|
|
func setbit(n uint32, pos uint) uint32 {
|
|
|
|
return n | (1 << pos)
|
|
|
|
}
|
|
|
|
func unsetbit(n uint32, pos uint) uint32 {
|
|
|
|
return n & ^(1 << pos)
|
|
|
|
}
|
|
|
|
func hasbit(n uint32, pos uint) bool {
|
|
|
|
return (n & (1 << pos)) != 0
|
|
|
|
}
|
|
|
|
|
2019-02-15 22:51:26 +03:00
|
|
|
func (item *Item) isPoint() bool {
|
2019-02-16 00:02:04 +03:00
|
|
|
return hasbit(item.head[0], 31)
|
2019-02-15 22:51:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (item *Item) setIsPoint(isPoint bool) {
|
|
|
|
if isPoint {
|
2019-02-16 00:02:04 +03:00
|
|
|
item.head[0] = setbit(item.head[0], 31)
|
2019-02-15 22:51:26 +03:00
|
|
|
} else {
|
2019-02-16 00:02:04 +03:00
|
|
|
item.head[0] = unsetbit(item.head[0], 31)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (item *Item) isPacked() bool {
|
|
|
|
return hasbit(item.head[0], 30)
|
|
|
|
}
|
|
|
|
func (item *Item) setIsPacked(isPacked bool) {
|
|
|
|
if isPacked {
|
|
|
|
item.head[0] = setbit(item.head[0], 30)
|
|
|
|
} else {
|
|
|
|
item.head[0] = unsetbit(item.head[0], 30)
|
2019-02-15 22:51:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (item *Item) fieldsLen() int {
|
2019-02-16 00:02:04 +03:00
|
|
|
return int(item.head[0] & 0x3FFFFFFF)
|
2019-02-15 22:51:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (item *Item) setFieldsLen(len int) {
|
2019-02-16 00:02:04 +03:00
|
|
|
item.head[0] = item.head[0]>>30<<30 | uint32(len)
|
2019-02-15 22:51:26 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (item *Item) idLen() int {
|
|
|
|
return int(item.head[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (item *Item) setIDLen(len int) {
|
|
|
|
item.head[1] = uint32(len)
|
|
|
|
}
|
|
|
|
|
2019-02-13 22:43:38 +03:00
|
|
|
// ID returns the items ID as a string
|
|
|
|
func (item *Item) ID() string {
|
|
|
|
return *(*string)((unsafe.Pointer)(&reflect.StringHeader{
|
2019-02-15 22:51:26 +03:00
|
|
|
Data: uintptr(unsafe.Pointer(item.data)) + uintptr(item.fieldsLen()),
|
|
|
|
Len: item.idLen(),
|
2019-02-13 22:43:38 +03:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fields returns the field values
|
2019-02-15 18:26:55 +03:00
|
|
|
func (item *Item) fields() []float64 {
|
2019-02-13 22:43:38 +03:00
|
|
|
return *(*[]float64)((unsafe.Pointer)(&reflect.SliceHeader{
|
|
|
|
Data: uintptr(unsafe.Pointer(item.data)),
|
2019-02-15 22:51:26 +03:00
|
|
|
Len: item.fieldsLen() / 8,
|
|
|
|
Cap: item.fieldsLen() / 8,
|
2019-02-13 22:43:38 +03:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Obj returns the geojson object
|
|
|
|
func (item *Item) Obj() geojson.Object {
|
2019-02-15 22:51:26 +03:00
|
|
|
if item.isPoint() {
|
2019-02-15 01:53:46 +03:00
|
|
|
return &(*pointItem)(unsafe.Pointer(item)).pt
|
|
|
|
}
|
|
|
|
return (*objItem)(unsafe.Pointer(item)).obj
|
2019-02-13 22:43:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a newly allocated Item
|
2019-02-16 00:02:04 +03:00
|
|
|
func New(id string, obj geojson.Object, packed bool) *Item {
|
2019-02-15 01:53:46 +03:00
|
|
|
var item *Item
|
|
|
|
if pt, ok := obj.(*geojson.SimplePoint); ok {
|
|
|
|
pitem := new(pointItem)
|
|
|
|
pitem.pt = *pt
|
|
|
|
item = (*Item)(unsafe.Pointer(pitem))
|
2019-02-15 22:51:26 +03:00
|
|
|
item.setIsPoint(true)
|
2019-02-15 01:53:46 +03:00
|
|
|
} else {
|
|
|
|
oitem := new(objItem)
|
|
|
|
oitem.obj = obj
|
|
|
|
item = (*Item)(unsafe.Pointer(oitem))
|
|
|
|
}
|
2019-02-16 00:02:04 +03:00
|
|
|
item.setIsPacked(packed)
|
2019-02-15 22:51:26 +03:00
|
|
|
item.setIDLen(len(id))
|
2019-02-15 21:11:40 +03:00
|
|
|
item.data = unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&id)).Data)
|
2019-02-13 22:43:38 +03:00
|
|
|
return item
|
|
|
|
}
|
|
|
|
|
|
|
|
// WeightAndPoints returns the memory weight and number of points for Item.
|
|
|
|
func (item *Item) WeightAndPoints() (weight, points int) {
|
2019-02-15 01:53:46 +03:00
|
|
|
_, objIsSpatial := item.Obj().(geojson.Spatial)
|
2019-02-13 22:43:38 +03:00
|
|
|
if objIsSpatial {
|
2019-02-15 01:53:46 +03:00
|
|
|
points = item.Obj().NumPoints()
|
2019-02-13 22:43:38 +03:00
|
|
|
weight = points * 16
|
2019-02-15 01:53:46 +03:00
|
|
|
} else if item.Obj() != nil {
|
|
|
|
weight = len(item.Obj().String())
|
2019-02-13 22:43:38 +03:00
|
|
|
}
|
2019-02-15 22:51:26 +03:00
|
|
|
weight += item.fieldsLen() + item.idLen()
|
2019-02-13 22:43:38 +03:00
|
|
|
return weight, points
|
|
|
|
}
|
|
|
|
|
|
|
|
// Less is a btree interface that compares if item is less than other item.
|
|
|
|
func (item *Item) Less(other btree.Item, ctx interface{}) bool {
|
2019-02-15 01:53:46 +03:00
|
|
|
value1 := item.Obj().String()
|
|
|
|
value2 := other.(*Item).Obj().String()
|
2019-02-13 22:43:38 +03:00
|
|
|
if value1 < value2 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if value1 > value2 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// the values match so we'll compare IDs, which are always unique.
|
|
|
|
return item.ID() < other.(*Item).ID()
|
|
|
|
}
|
|
|
|
|
2019-02-15 18:26:55 +03:00
|
|
|
// CopyOverFields overwriting previous fields. Accepts an *Item or []float64
|
|
|
|
func (item *Item) CopyOverFields(from interface{}) {
|
|
|
|
var values []float64
|
|
|
|
switch from := from.(type) {
|
|
|
|
case *Item:
|
|
|
|
values = from.fields()
|
|
|
|
case []float64:
|
|
|
|
values = from
|
|
|
|
}
|
2019-02-13 22:43:38 +03:00
|
|
|
fieldBytes := floatsToBytes(values)
|
|
|
|
oldData := item.dataBytes()
|
2019-02-15 22:51:26 +03:00
|
|
|
newData := make([]byte, len(fieldBytes)+item.idLen())
|
2019-02-13 22:43:38 +03:00
|
|
|
copy(newData, fieldBytes)
|
2019-02-15 22:51:26 +03:00
|
|
|
copy(newData[len(fieldBytes):], oldData[item.fieldsLen():])
|
|
|
|
item.setFieldsLen(len(fieldBytes))
|
2019-02-13 22:43:38 +03:00
|
|
|
if len(newData) > 0 {
|
|
|
|
item.data = unsafe.Pointer(&newData[0])
|
|
|
|
} else {
|
|
|
|
item.data = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFieldAt(data unsafe.Pointer, index int) float64 {
|
|
|
|
return *(*float64)(unsafe.Pointer(uintptr(data) + uintptr(index*8)))
|
|
|
|
}
|
|
|
|
|
|
|
|
func setFieldAt(data unsafe.Pointer, index int, value float64) {
|
|
|
|
*(*float64)(unsafe.Pointer(uintptr(data) + uintptr(index*8))) = value
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetField set a field value at specified index.
|
|
|
|
func (item *Item) SetField(index int, value float64) (updated bool) {
|
2019-02-15 22:51:26 +03:00
|
|
|
numFields := item.fieldsLen() / 8
|
2019-02-13 22:43:38 +03:00
|
|
|
if index < numFields {
|
|
|
|
// field exists
|
|
|
|
if getFieldAt(item.data, index) == value {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// make room for new field
|
|
|
|
oldBytes := item.dataBytes()
|
2019-02-15 22:51:26 +03:00
|
|
|
newData := make([]byte, (index+1)*8+item.idLen())
|
2019-02-13 22:43:38 +03:00
|
|
|
// copy the existing fields
|
2019-02-15 22:51:26 +03:00
|
|
|
copy(newData, oldBytes[:item.fieldsLen()])
|
2019-02-13 22:43:38 +03:00
|
|
|
// copy the id
|
2019-02-15 22:51:26 +03:00
|
|
|
copy(newData[(index+1)*8:], oldBytes[item.fieldsLen():])
|
2019-02-13 22:43:38 +03:00
|
|
|
// update the fields length
|
2019-02-15 22:51:26 +03:00
|
|
|
item.setFieldsLen((index + 1) * 8)
|
2019-02-13 22:43:38 +03:00
|
|
|
// update the raw data
|
|
|
|
item.data = unsafe.Pointer(&newData[0])
|
|
|
|
}
|
|
|
|
// set the new field
|
|
|
|
setFieldAt(item.data, index, value)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (item *Item) dataBytes() []byte {
|
|
|
|
return *(*[]byte)((unsafe.Pointer)(&reflect.SliceHeader{
|
|
|
|
Data: uintptr(unsafe.Pointer(item.data)),
|
2019-02-15 22:51:26 +03:00
|
|
|
Len: item.fieldsLen() + item.idLen(),
|
|
|
|
Cap: item.fieldsLen() + item.idLen(),
|
2019-02-13 22:43:38 +03:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
func floatsToBytes(f []float64) []byte {
|
|
|
|
return *(*[]byte)((unsafe.Pointer)(&reflect.SliceHeader{
|
|
|
|
Data: ((*reflect.SliceHeader)(unsafe.Pointer(&f))).Data,
|
|
|
|
Len: len(f) * 8,
|
|
|
|
Cap: len(f) * 8,
|
|
|
|
}))
|
|
|
|
}
|
2019-02-15 18:26:55 +03:00
|
|
|
|
|
|
|
// ForEachField iterates over each field. The count param is the number of
|
|
|
|
// iterations. When count is less than zero, then all fields are returns.
|
|
|
|
func (item *Item) ForEachField(count int, iter func(value float64) bool) {
|
|
|
|
if item == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
fields := item.fields()
|
|
|
|
var n int
|
|
|
|
if count < 0 {
|
|
|
|
n = len(fields)
|
|
|
|
} else {
|
|
|
|
n = count
|
|
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
var field float64
|
|
|
|
if i < len(fields) {
|
|
|
|
field = fields[i]
|
|
|
|
}
|
|
|
|
if !iter(field) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-16 00:02:04 +03:00
|
|
|
// Packed returns true when the item's fields are packed
|
|
|
|
func (item *Item) Packed() bool {
|
|
|
|
return item == nil || item.isPacked()
|
|
|
|
}
|
|
|
|
|
2019-02-15 18:26:55 +03:00
|
|
|
// GetField returns the value for a field at index.
|
|
|
|
func (item *Item) GetField(index int) float64 {
|
2019-02-16 00:02:04 +03:00
|
|
|
if index < 0 {
|
|
|
|
panic("index out of range")
|
|
|
|
}
|
2019-02-15 18:26:55 +03:00
|
|
|
if item == nil {
|
|
|
|
return 0
|
|
|
|
}
|
2019-02-16 00:02:04 +03:00
|
|
|
if item.Packed() {
|
2019-02-15 22:51:26 +03:00
|
|
|
var fvalue float64
|
|
|
|
var idx int
|
|
|
|
item.ForEachField(-1, func(value float64) bool {
|
|
|
|
if idx == index {
|
|
|
|
fvalue = value
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
idx++
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return fvalue
|
|
|
|
}
|
|
|
|
numFields := item.fieldsLen() / 8
|
2019-02-15 18:26:55 +03:00
|
|
|
if index < numFields {
|
|
|
|
return getFieldAt(item.data, index)
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasFields returns true when item has fields
|
|
|
|
func (item *Item) HasFields() bool {
|
2019-02-15 22:51:26 +03:00
|
|
|
return item != nil && item.fieldsLen() > 0
|
2019-02-15 18:26:55 +03:00
|
|
|
}
|