tile38/internal/object/object_binary.go

169 lines
3.1 KiB
Go
Raw Permalink Normal View History

2022-09-21 00:20:53 +03:00
package object
import (
2022-09-21 20:40:37 +03:00
"encoding/binary"
"unsafe"
2022-09-21 00:20:53 +03:00
"github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry"
"github.com/tidwall/tile38/internal/field"
)
2022-09-21 20:40:37 +03:00
type pointObject struct {
base Object
pt geojson.SimplePoint
}
type geoObject struct {
base Object
geo geojson.Object
}
const opoint = 1
const ogeo = 2
2022-09-21 00:20:53 +03:00
type Object struct {
2022-09-21 20:40:37 +03:00
head string // tuple (kind,expires,id)
fields field.List
}
func (o *Object) geo() geojson.Object {
if o != nil {
switch o.head[0] {
case opoint:
return &(*pointObject)(unsafe.Pointer(o)).pt
case ogeo:
return (*geoObject)(unsafe.Pointer(o)).geo
}
}
return nil
}
// uvarint is a slightly modified version of binary.Uvarint, and it's a little
// faster. But it lacks overflow checks which are not needed for our use.
func uvarint(s string) (uint64, int) {
var x uint64
for i := 0; i < len(s); i++ {
b := s[i]
if b < 0x80 {
return x | uint64(b)<<(i*7), i + 1
}
x |= uint64(b&0x7f) << (i * 7)
}
return 0, 0
}
func varint(s string) (int64, int) {
ux, n := uvarint(s)
x := int64(ux >> 1)
if ux&1 != 0 {
x = ^x
}
return x, n
2022-09-21 00:20:53 +03:00
}
func (o *Object) ID() string {
2022-09-22 04:29:01 +03:00
if o.head[1] == 0 {
return o.head[2:]
2022-09-21 00:20:53 +03:00
}
2022-09-21 20:40:37 +03:00
_, n := varint(o.head[1:])
return o.head[1+n:]
2022-09-21 00:20:53 +03:00
}
func (o *Object) Fields() field.List {
return o.fields
}
func (o *Object) Expires() int64 {
2022-09-21 20:40:37 +03:00
ex, _ := varint(o.head[1:])
return ex
2022-09-21 00:20:53 +03:00
}
func (o *Object) Rect() geometry.Rect {
2022-09-21 20:40:37 +03:00
ogeo := o.geo()
if ogeo == nil {
2022-09-21 00:20:53 +03:00
return geometry.Rect{}
}
2022-09-21 20:40:37 +03:00
return ogeo.Rect()
2022-09-21 00:20:53 +03:00
}
func (o *Object) Geo() geojson.Object {
2022-09-21 20:40:37 +03:00
return o.geo()
2022-09-21 00:20:53 +03:00
}
func (o *Object) String() string {
2022-09-21 20:40:37 +03:00
ogeo := o.geo()
if ogeo == nil {
2022-09-21 00:20:53 +03:00
return ""
}
2022-09-21 20:40:37 +03:00
return ogeo.String()
2022-09-21 00:20:53 +03:00
}
func (o *Object) IsSpatial() bool {
2022-09-21 20:40:37 +03:00
_, ok := o.geo().(geojson.Spatial)
2022-09-21 00:20:53 +03:00
return ok
}
func (o *Object) Weight() int {
var weight int
weight += len(o.ID())
2022-09-22 04:29:01 +03:00
ogeo := o.geo()
if ogeo != nil {
if o.IsSpatial() {
weight += ogeo.NumPoints() * 16
} else {
weight += len(ogeo.String())
}
2022-09-21 00:20:53 +03:00
}
weight += o.Fields().Weight()
return weight
}
2022-09-21 20:40:37 +03:00
func makeHead(kind byte, id string, expires int64) string {
var exb [20]byte
2022-09-22 04:29:01 +03:00
exn := 1
if expires != 0 {
exn = binary.PutVarint(exb[:], expires)
}
2022-09-21 20:40:37 +03:00
n := 1 + exn + len(id)
head := make([]byte, n)
head[0] = kind
copy(head[1:], exb[:exn])
copy(head[1+exn:], id)
return *(*string)(unsafe.Pointer(&head))
}
func newPoint(id string, pt geometry.Point, expires int64, fields field.List,
) *Object {
return (*Object)(unsafe.Pointer(&pointObject{
Object{
head: makeHead(opoint, id, expires),
fields: fields,
},
geojson.SimplePoint{Point: pt},
}))
}
func newGeo(id string, geo geojson.Object, expires int64, fields field.List,
) *Object {
return (*Object)(unsafe.Pointer(&geoObject{
Object{
head: makeHead(ogeo, id, expires),
fields: fields,
},
geo,
}))
}
2022-09-21 20:03:53 +03:00
func New(id string, geo geojson.Object, expires int64, fields field.List,
2022-09-21 00:20:53 +03:00
) *Object {
2022-09-21 20:40:37 +03:00
switch p := geo.(type) {
case *geojson.SimplePoint:
return newPoint(id, p.Base(), expires, fields)
case *geojson.Point:
if p.IsSimple() {
return newPoint(id, p.Base(), expires, fields)
}
2022-09-21 00:20:53 +03:00
}
2022-09-21 20:40:37 +03:00
return newGeo(id, geo, expires, fields)
2022-09-21 00:20:53 +03:00
}