1package bundle23import (4 "encoding/binary"5 "fmt"6 "math"7)89// The ATLASLOC container. Point features are overwhelmingly numbers, and10// numbers written as text cost several times what they measure, so a world's11// points travel as parallel little-endian arrays rather than as JSON.12const (13 // LocationMagic opens every packed payload.14 LocationMagic = "ATLASLOC"1516 // LocationVersion is the packing this package reads and writes.17 //18 // Version 2 added the shard column: a world split into layers offers one19 // at a time, and without it every layer's locations drew over every20 // other. Version 3 moved meaning without moving a byte -- Owner now21 // indexes the world payload's collections array rather than a flattened22 // category order, and the fifth column reads as Member, the id of the23 // area feature containing the point.24 LocationVersion = 32526 // locationHeader is the fixed prologue: 8 bytes of magic, a uint1627 // version, a uint32 count, and two reserved bytes that keep every column28 // that follows four-byte aligned.29 locationHeader = 1630)3132// Location is one point feature as the packed payload carries it.33//34// Owner is the position of the feature's collection in the world payload's35// collections array, which is the only collection identity a reader needs36// once the payload has listed the collections in that same order. Member is37// the id of the area feature containing this point, zero for none. Shard is38// the layer of a split world the point belongs to, zero when the world is39// whole.40//41// Lat and Lng are float64 here and single precision on the wire, which42// resolves far finer than a world is drawn. ID, Member, and Shard are int6443// here and signed 32-bit on the wire.44type Location struct {45 ID int6446 Title string47 Lat float6448 Lng float6449 Member int6450 Shard int6451 Owner uint1652}5354// PackLocations lays the locations out as parallel arrays: the fixed header,55// then five uint32 columns, then the title offsets, then the uint16 owner56// column, then the concatenated title bytes. Four-byte fields come first so a57// reader can view each column directly without copying or realigning.58//59// The encoding is total and deterministic: the same locations in the same60// order always produce the same bytes.61func PackLocations(locations []Location) []byte {62 count := len(locations)6364 titles := make([]byte, 0, count*12)65 offsets := make([]uint32, count+1)66 for index, location := range locations {67 offsets[index] = uint32(len(titles))68 titles = append(titles, location.Title...)69 }70 offsets[count] = uint32(len(titles))7172 out := make([]byte, packedSize(count)+len(titles))73 copy(out, LocationMagic)74 binary.LittleEndian.PutUint16(out[8:], LocationVersion)75 binary.LittleEndian.PutUint32(out[10:], uint32(count))76 // out[14:16] is reserved, and keeps the columns four-byte aligned.7778 at := locationHeader79 put32 := func(value uint32) {80 binary.LittleEndian.PutUint32(out[at:], value)81 at += 482 }83 for _, location := range locations {84 put32(uint32(int32(location.ID)))85 }86 for _, location := range locations {87 put32(math.Float32bits(float32(location.Lat)))88 }89 for _, location := range locations {90 put32(math.Float32bits(float32(location.Lng)))91 }92 for _, location := range locations {93 put32(uint32(int32(location.Member)))94 }95 for _, location := range locations {96 put32(uint32(int32(location.Shard)))97 }98 for _, offset := range offsets {99 put32(offset)100 }101 for _, location := range locations {102 binary.LittleEndian.PutUint16(out[at:], location.Owner)103 at += 2104 }105 copy(out[at:], titles)106 return out107}108109// UnpackLocations decodes a packed payload into structs. It is the whole-110// payload face of [OpenPacked], for callers that want the locations as111// ordinary Go values.112func UnpackLocations(data []byte) ([]Location, error) {113 packed, err := OpenPacked(data)114 if err != nil {115 return nil, err116 }117 return packed.All(), nil118}119120// Packed is a decoded view over an ATLASLOC payload that borrows the caller's121// bytes. Nothing is copied when it opens and nothing is allocated per122// location, so a reader can walk a million points and pay only for the fields123// it actually reads.124//125// The columns are read on demand through encoding/binary rather than126// reinterpreted as typed slices: Go cannot alias a []byte as a []uint32127// without unsafe, and a bundle's bytes arrive at whatever alignment the128// archive gave them. A JavaScript reader building typed-array views over the129// same buffer sees exactly these columns at exactly these offsets, which is130// why the header pads to sixteen bytes.131//132// The view is read-only and must not outlive the bytes it was opened over.133type Packed struct {134 data []byte135 count int136 id int // byte offset of each column within data137 lat int138 lng int139 member int140 shard int141 offset int142 owner int143 titles int144}145146// OpenPacked validates a packed payload's header and returns a view over it.147// It refuses a payload with the wrong magic, an unreadable version, or a148// length that cannot hold the columns its count promises.149func OpenPacked(data []byte) (*Packed, error) {150 if len(data) < locationHeader || string(data[:8]) != LocationMagic {151 return nil, fmt.Errorf("location payload is not in the expected form")152 }153 if version := binary.LittleEndian.Uint16(data[8:]); version != LocationVersion {154 return nil, fmt.Errorf("location payload is version %d, and this reads %d", version, LocationVersion)155 }156 count := int(binary.LittleEndian.Uint32(data[10:]))157 if count < 0 || len(data) < packedSize(count) {158 return nil, fmt.Errorf("location payload is truncated")159 }160161 packed := &Packed{data: data, count: count}162 at := locationHeader163 for _, column := range []*int{&packed.id, &packed.lat, &packed.lng, &packed.member, &packed.shard} {164 *column = at165 at += count * 4166 }167 packed.offset = at168 at += (count + 1) * 4169 packed.owner = at170 at += count * 2171 packed.titles = at172173 // The title region is variable, so it is the one thing the header cannot174 // bound. Walking the offsets once at open costs a pass over one column and175 // buys a view that can never panic on a corrupt payload, which matters176 // because these bytes arrive from a file somebody else wrote.177 var previous uint32178 for index := 0; index <= count; index++ {179 reach := packed.column(packed.offset, index)180 if reach < previous {181 return nil, fmt.Errorf("location title offsets run backwards at %d", index)182 }183 previous = reach184 }185 if packed.titles+int(previous) > len(data) {186 return nil, fmt.Errorf("location titles are truncated")187 }188 return packed, nil189}190191// packedSize is the fixed part of a payload holding count locations,192// everything but the title bytes.193func packedSize(count int) int {194 return locationHeader + count*20 + (count+1)*4 + count*2195}196197// Len is how many locations the payload holds.198func (p *Packed) Len() int { return p.count }199200// ID, Lat, Lng, Member, Shard, and Owner read one column of one location.201// The index is not bounds-checked beyond what Go's own slicing does.202func (p *Packed) ID(index int) int64 { return int64(int32(p.column(p.id, index))) }203func (p *Packed) Lat(index int) float64 { return float64(math.Float32frombits(p.column(p.lat, index))) }204func (p *Packed) Lng(index int) float64 { return float64(math.Float32frombits(p.column(p.lng, index))) }205func (p *Packed) Member(index int) int64 { return int64(int32(p.column(p.member, index))) }206func (p *Packed) Shard(index int) int64 { return int64(int32(p.column(p.shard, index))) }207func (p *Packed) Owner(index int) uint16 {208 return binary.LittleEndian.Uint16(p.data[p.owner+index*2:])209}210211// TitleBytes returns one location's title as a slice of the payload, with no212// copy. The bytes belong to the payload and must not be modified.213func (p *Packed) TitleBytes(index int) []byte {214 start := p.titles + int(p.titleStart(index))215 end := p.titles + int(p.titleEnd(index))216 return p.data[start:end]217}218219// Title returns one location's title as a string, which is the one place a220// read allocates.221func (p *Packed) Title(index int) string { return string(p.TitleBytes(index)) }222223// At assembles one location as a struct.224func (p *Packed) At(index int) Location {225 return Location{226 ID: p.ID(index),227 Title: p.Title(index),228 Lat: p.Lat(index),229 Lng: p.Lng(index),230 Member: p.Member(index),231 Shard: p.Shard(index),232 Owner: p.Owner(index),233 }234}235236// All assembles every location as a struct. Packing the result again237// reproduces the payload byte for byte.238func (p *Packed) All() []Location {239 out := make([]Location, p.count)240 for index := range out {241 out[index] = p.At(index)242 }243 return out244}245246func (p *Packed) column(base, index int) uint32 {247 return binary.LittleEndian.Uint32(p.data[base+index*4:])248}249250func (p *Packed) titleStart(index int) uint32 { return p.column(p.offset, index) }251func (p *Packed) titleEnd(index int) uint32 { return p.column(p.offset, index+1) }