Documentation
¶
Index ¶
- Constants
- func DecodeDenseNodes(groupData []byte, buf *DenseNodesBuf, info *DenseInfoBuf) error
- type BlockReader
- type Decompressor
- type DenseInfoBuf
- type DenseNodesBuf
- type GroupScanner
- func (gs *GroupScanner) DecodeDenseNodes(buf *DenseNodesBuf, info *DenseInfoBuf) error
- func (gs *GroupScanner) Err() error
- func (gs *GroupScanner) Next() bool
- func (gs *GroupScanner) NodeScanner() NodeScanner
- func (gs *GroupScanner) RelationScanner() RelationScanner
- func (gs *GroupScanner) Type() GroupType
- func (gs *GroupScanner) WayScanner() WayScanner
- type GroupType
- type Header
- type HeaderBBox
- type InfoBuf
- type NodeBuf
- type NodeScanner
- type PrimitiveBlock
- type RelationBuf
- type RelationScanner
- type WayBuf
- type WayScanner
Examples ¶
Constants ¶
const ( MemberTypeNode = int32(0) MemberTypeWay = int32(1) MemberTypeRelation = int32(2) )
Member type constants for Relation members.
Variables ¶
This section is empty.
Functions ¶
func DecodeDenseNodes ¶
func DecodeDenseNodes(groupData []byte, buf *DenseNodesBuf, info *DenseInfoBuf) error
DecodeDenseNodes decodes a DenseNodes PrimitiveGroup into buf. groupData is the raw bytes of a PrimitiveGroup message (from GroupScanner.groupData). Resets all slices to [:0] then appends. Delta-decodes IDs, Lats, Lons. Pass a non-nil info to also decode DenseInfo metadata; nil skips it.
Types ¶
type BlockReader ¶
type BlockReader struct {
// contains filtered or unexported fields
}
BlockReader reads PBF FileBlocks from an io.Reader. The zero value is ready to use after Reset, so a BlockReader can be embedded in a caller-owned struct and reused without allocating.
Call Next to advance and Blob to get the current block's raw Blob protobuf message, or NextInto to read into caller-owned storage. Type and Offset describe the current block. Use a Decompressor to decompress the blob.
BlockReader is not safe for concurrent use.
Example ¶
package main
import (
"fmt"
"os"
"github.com/invisiblefunnel/osmbr"
)
func main() {
f, err := os.Open("testdata/us-virgin-islands-260414.osm.pbf")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
br := osmbr.NewBlockReader(f)
for br.Next() {
fmt.Printf("type=%s offset=%d len=%d\n", br.Type(), br.Offset(), len(br.Blob()))
break // first block only for the example
}
if err := br.Err(); err != nil {
fmt.Println(err)
}
}
Output: type=OSMHeader offset=0 len=193
func NewBlockReader ¶
func NewBlockReader(r io.Reader) *BlockReader
NewBlockReader returns a BlockReader that reads PBF blocks from r.
Equivalent to declaring a BlockReader and calling Reset; use that form to keep the reader inside a struct you already own.
func (*BlockReader) Blob ¶
func (br *BlockReader) Blob() []byte
Blob returns the raw Blob protobuf message bytes read by the most recent call to Next. It is invalidated by the next call to Next or NextInto, and is empty once Next has returned false.
func (*BlockReader) Err ¶
func (br *BlockReader) Err() error
Err returns the first non-EOF error encountered, which is also the only one: reading stops at the first failure. Reset clears it.
func (*BlockReader) Next ¶
func (br *BlockReader) Next() bool
Next reads the next FileBlock into br's own storage, which Blob then returns. It reports false on EOF or error; call Err to distinguish them. Errors are sticky, so a loop may check Err once after it ends.
Use NextInto instead when the blob must outlive the next read, such as when handing blocks to worker goroutines.
func (*BlockReader) NextInto ¶ added in v0.3.0
func (br *BlockReader) NextInto(dst []byte) ([]byte, bool)
NextInto reads the next FileBlock into dst, overwriting it, and returns the slice holding the raw Blob protobuf message. When cap(dst) is too small it allocates a larger slice instead, so the returned slice may have a different backing array than dst.
It reports false on EOF or error, returning dst[:0] so a caller that pools buffers never loses one. Call Err to distinguish EOF from an error. After a successful call, Type and Offset describe the current block.
Errors are sticky. A failure leaves the underlying reader positioned mid-block, where the next four bytes are payload rather than a length prefix, so reading on would decode garbage as blocks; once a call fails, every later one reports false until Reset.
Example ¶
package main
import (
"fmt"
"os"
"sync"
"github.com/invisiblefunnel/osmbr"
)
func main() {
f, err := os.Open("testdata/us-virgin-islands-260414.osm.pbf")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
// NextInto reads into caller-owned storage, so a blob stays valid after
// the reader moves on — what a producer feeding worker goroutines needs.
// On EOF or error it hands the buffer back, so nothing leaks out of the
// pool.
var pool sync.Pool
br := osmbr.NewBlockReader(f)
buf, _ := pool.Get().([]byte)
blob, ok := br.NextInto(buf)
if !ok {
pool.Put(blob)
fmt.Println(br.Err())
return
}
fmt.Printf("type=%s len=%d\n", br.Type(), len(blob))
pool.Put(blob[:0])
}
Output: type=OSMHeader len=193
func (*BlockReader) Offset ¶
func (br *BlockReader) Offset() int64
Offset returns the byte offset where the current block starts in the underlying reader. Use with io.Seeker to re-read a specific block later.
func (*BlockReader) Reset ¶ added in v0.2.0
func (br *BlockReader) Reset(r io.Reader)
Reset reuses br to read from r, preserving buffer capacities. Use this to walk many files with one BlockReader instead of allocating a new one per file.
func (*BlockReader) Type ¶
func (br *BlockReader) Type() string
Type returns the block type ("OSMHeader" or "OSMData").
type Decompressor ¶
type Decompressor struct {
// SkipChecksum turns off validation of the Adler-32 trailer on zlib blobs.
//
// Verification is on by default and costs roughly 14% of decompression
// time. Inflating catches most corruption on its own, and Decompress
// independently requires the output to end exactly at Blob.raw_size, but
// neither covers a stored (uncompressed) DEFLATE block: a flipped bit
// there changes neither the stream's structure nor its length, so the
// checksum is the only thing standing between it and the caller.
//
// Set it before the first call to Decompress, and only for input whose
// integrity is already assured.
SkipChecksum bool
// contains filtered or unexported fields
}
Decompressor parses and decompresses raw PBF Blob messages. Allocate one per goroutine and reuse across blocks to avoid per-block allocations.
Decompressor is not safe for concurrent use.
Example ¶
package main
import (
"fmt"
"os"
"github.com/invisiblefunnel/osmbr"
)
func main() {
f, err := os.Open("testdata/us-virgin-islands-260414.osm.pbf")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
var dec osmbr.Decompressor
br := osmbr.NewBlockReader(f)
if !br.Next() {
fmt.Println("no blocks")
return
}
data, err := dec.Decompress(br.Blob())
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("decompressed %d bytes\n", len(data))
}
Output: decompressed 179 bytes
func (*Decompressor) Decompress ¶
func (d *Decompressor) Decompress(blob []byte) ([]byte, error)
Decompress parses a raw Blob protobuf message and returns the decompressed payload. The returned slice points into the Decompressor's own storage and is valid until the next call to Decompress, whatever compression the Blob used.
type DenseInfoBuf ¶
type DenseInfoBuf struct {
Versions []int32
Timestamps []int64 // delta-decoded; milliseconds since Unix epoch
Changesets []int64 // delta-decoded
UIDs []int32 // delta-decoded
UserSIDs []int32 // delta-decoded; indices into the block's string table
Visibles []bool
}
DenseInfoBuf holds optional per-node metadata arrays decoded from a DenseInfo message. All slices are grown as needed (capacity preserved across calls). Pass a non-nil *DenseInfoBuf to DecodeDenseNodes to populate; nil skips it.
DecodeDenseNodes guarantees every array here is either empty or exactly as long as DenseNodesBuf.IDs, so an array that is non-empty can be indexed by node position without a bounds check of its own. A file that violates this is rejected rather than decoded. An array is empty whenever the group omits that field — including when the group carries no DenseInfo at all, which clears anything a previous group left behind.
Delta-decoded fields: Timestamps, Changesets, UIDs, UserSIDs. Non-delta fields: Versions.
Note UserSIDs is delta-coded here while the single-entity InfoBuf.UserSID is not: the PBF schema declares DenseInfo.user_sid as a delta-coded sint32 and Info.user_sid as a plain uint32.
type DenseNodesBuf ¶
DenseNodesBuf is caller-managed memory for decoding a DenseNodes group. Allocate once and reuse across blocks to avoid per-block allocations. After warm-up, all slices grow to accommodate the largest block seen and are then reused without further allocation.
IDs, Lats, and Lons contain delta-decoded absolute values. To convert Lats[i] and Lons[i] to nanodegrees:
lat_nanodeg = Lats[i] * int64(pb.Granularity) + pb.LatOffset lon_nanodeg = Lons[i] * int64(pb.Granularity) + pb.LonOffset
KeysVals encodes tags as a flat array of string-table indices:
(keyIdx valIdx)* 0 per node, repeated
The 0 value delimits one node's tags from the next. KeysVals is not validated: a malformed file may end mid-pair, and the indices themselves are not checked against the string table, so bound the pair read and use NumStrings before calling String. Example iteration:
j := 0
for i := range buf.IDs {
// j+1 keeps a trailing key with no value from reading past the end.
for j+1 < len(buf.KeysVals) && buf.KeysVals[j] != 0 {
key := pb.String(int(buf.KeysVals[j]))
val := pb.String(int(buf.KeysVals[j+1]))
j += 2
}
j++ // skip the 0 delimiter
}
Example ¶
package main
import (
"fmt"
"os"
"github.com/invisiblefunnel/osmbr"
)
func main() {
f, err := os.Open("testdata/us-virgin-islands-260414.osm.pbf")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
var (
dec osmbr.Decompressor
pb osmbr.PrimitiveBlock
dnBuf osmbr.DenseNodesBuf
)
br := osmbr.NewBlockReader(f)
for br.Next() {
if br.Type() != "OSMData" {
continue
}
data, err := dec.Decompress(br.Blob())
if err != nil {
fmt.Println(err)
return
}
if err := pb.DecodeFrom(data); err != nil {
fmt.Println(err)
return
}
gs := pb.Groups()
for gs.Next() {
if gs.Type() != osmbr.GroupTypeDense {
continue
}
if err := gs.DecodeDenseNodes(&dnBuf, nil); err != nil {
fmt.Println(err)
return
}
// Convert the first node's raw lat/lon to nanodegrees.
latNanodeg := dnBuf.Lats[0]*int64(pb.Granularity) + pb.LatOffset
lonNanodeg := dnBuf.Lons[0]*int64(pb.Granularity) + pb.LonOffset
fmt.Printf("first node id=%d lat=%d lon=%d\n",
dnBuf.IDs[0], latNanodeg, lonNanodeg)
return
}
}
}
Output: first node id=38344686 lat=17757614800 lon=-64585070900
type GroupScanner ¶
type GroupScanner struct {
// contains filtered or unexported fields
}
GroupScanner iterates over PrimitiveGroups within a PrimitiveBlock. Obtain one via PrimitiveBlock.Groups. GroupScanner is a value type.
func (*GroupScanner) DecodeDenseNodes ¶
func (gs *GroupScanner) DecodeDenseNodes(buf *DenseNodesBuf, info *DenseInfoBuf) error
DecodeDenseNodes decodes the current DenseNodes group into buf. Only valid when Type() == GroupTypeDense. Pass a non-nil info to also decode per-node metadata; nil skips it.
func (*GroupScanner) Err ¶ added in v0.1.1
func (gs *GroupScanner) Err() error
Err returns the first error encountered during iteration.
func (*GroupScanner) Next ¶
func (gs *GroupScanner) Next() bool
Next advances to the next PrimitiveGroup. Returns false on EOF or error. Call Err to distinguish between them.
func (*GroupScanner) NodeScanner ¶
func (gs *GroupScanner) NodeScanner() NodeScanner
NodeScanner returns a NodeScanner for the current group. Only valid when Type() == GroupTypeNodes. Note: non-dense nodes are rare in practice; most OSM data uses DenseNodes.
func (*GroupScanner) RelationScanner ¶
func (gs *GroupScanner) RelationScanner() RelationScanner
RelationScanner returns a RelationScanner for the current group. Only valid when Type() == GroupTypeRelations.
func (*GroupScanner) Type ¶
func (gs *GroupScanner) Type() GroupType
Type returns the GroupType of the current group.
func (*GroupScanner) WayScanner ¶
func (gs *GroupScanner) WayScanner() WayScanner
WayScanner returns a WayScanner for the current group. Only valid when Type() == GroupTypeWays.
type Header ¶
type Header struct {
// BBox is the bounding box of the data in nanodegrees.
// Left and Right are longitude; Top and Bottom are latitude.
BBox HeaderBBox
// RequiredFeatures lists features a parser must support (e.g. "OsmSchema-V0.6", "DenseNodes").
RequiredFeatures []string
// OptionalFeatures lists features a parser may optionally handle (e.g. "Sort.Type_then_ID").
OptionalFeatures []string
// WritingProgram identifies the tool that created the file (e.g. "osmium/1.16.0").
WritingProgram string
// Source identifies the data source.
Source string
// ReplicationTimestamp is seconds since Unix epoch for the replication state.
ReplicationTimestamp int64
// ReplicationSequenceNumber is the replication sequence number.
ReplicationSequenceNumber int64
// ReplicationBaseURL is the base URL for replication diff files.
ReplicationBaseURL string
}
Header holds the decoded contents of an OSMHeader block.
func DecodeHeader ¶
DecodeHeader decodes a decompressed OSMHeader block.
type HeaderBBox ¶
type HeaderBBox struct {
Left, Right, Top, Bottom int64
}
HeaderBBox holds bounding box coordinates in nanodegrees.
type InfoBuf ¶
type InfoBuf struct {
Version int32
Timestamp int64 // milliseconds since Unix epoch
Changeset int64
UID int32
UserSID uint32 // index into the block's string table
Visible bool
HasVisible bool // false if the visible field was absent
}
InfoBuf holds optional per-entity metadata decoded from an Info message. Pass a non-nil *InfoBuf to WayScanner.Next, RelationScanner.Next, or NodeScanner.Next to populate it; nil skips decoding entirely.
Each Next zeroes the buffer before decoding, so one InfoBuf can be reused across a whole file: an entity carrying no Info reads back as the zero value rather than as the previous entity's metadata.
type NodeBuf ¶
type NodeBuf struct {
Keys []uint32 // string table indices for tag keys
Vals []uint32 // string table indices for tag values
}
NodeBuf is caller-managed memory for decoding individual Node entities. Non-dense nodes are rare in practice; most OSM data uses DenseNodes. Reuse across calls to avoid per-node allocations.
type NodeScanner ¶
type NodeScanner struct {
// contains filtered or unexported fields
}
NodeScanner iterates over individual Node messages in a PrimitiveGroup. Obtain one via GroupScanner.NodeScanner. NodeScanner is a value type. Note: in practice, OSM planet files and extracts use DenseNodes exclusively.
func (*NodeScanner) Err ¶
func (ns *NodeScanner) Err() error
Err returns the first error encountered during iteration.
func (*NodeScanner) Next ¶
func (ns *NodeScanner) Next(buf *NodeBuf, info *InfoBuf) (id, lat, lon int64, ok bool)
Next decodes the next Node into buf and returns its ID, lat, and lon. Resets buf slices to [:0] then appends (capacity preserved). Returns (0, 0, 0, false) when no more nodes remain. lat and lon are raw sint64 values. Convert to nanodegrees:
lat_nanodeg = lat * int64(pb.Granularity) + pb.LatOffset
Pass a non-nil info to also decode the Node's Info; nil skips it.
type PrimitiveBlock ¶
type PrimitiveBlock struct {
// Granularity is the coordinate granularity in nanodegrees (default 100).
// To convert a raw lat/lon integer to nanodegrees:
//
// lat_nanodeg = Lats[i] * int64(Granularity) + LatOffset
// lon_nanodeg = Lons[i] * int64(Granularity) + LonOffset
Granularity int32
// LatOffset is the latitude offset in nanodegrees (default 0).
LatOffset int64
// LonOffset is the longitude offset in nanodegrees (default 0).
LonOffset int64
// DateGranularity is the timestamp granularity in milliseconds (default 1000).
DateGranularity int32
// contains filtered or unexported fields
}
PrimitiveBlock holds the decoded metadata and string table for an OSMData block. Call DecodeFrom to populate from a Decompressor's output. Call Groups to iterate groups.
String table entries are zero-copy slices into the data passed to DecodeFrom. They are only valid until the next call to DecodeFrom on this block or to Decompressor.Decompress on the underlying decompressor (which reuses its buffer).
func (*PrimitiveBlock) DecodeFrom ¶
func (pb *PrimitiveBlock) DecodeFrom(data []byte) error
DecodeFrom populates the PrimitiveBlock from decompressed OSMData block bytes (typically the result of Decompressor.Decompress).
String table entries reference data's memory. Copy entries you need to retain past the next Decompressor.Decompress or DecodeFrom call.
func (*PrimitiveBlock) Groups ¶
func (pb *PrimitiveBlock) Groups() GroupScanner
Groups returns a GroupScanner for iterating over the PrimitiveGroups in this block. The scanner re-reads from the original block data.
Example ¶
package main
import (
"fmt"
"os"
"github.com/invisiblefunnel/osmbr"
)
func main() {
f, err := os.Open("testdata/us-virgin-islands-260414.osm.pbf")
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
var (
dec osmbr.Decompressor
pb osmbr.PrimitiveBlock
)
br := osmbr.NewBlockReader(f)
for br.Next() {
if br.Type() != "OSMData" {
continue
}
data, err := dec.Decompress(br.Blob())
if err != nil {
fmt.Println(err)
return
}
if err := pb.DecodeFrom(data); err != nil {
fmt.Println(err)
return
}
gs := pb.Groups()
for gs.Next() {
fmt.Printf("group type=%d\n", gs.Type())
}
break // one OSMData block is enough for the example
}
}
Output: group type=2
func (*PrimitiveBlock) NumStrings ¶
func (pb *PrimitiveBlock) NumStrings() int
NumStrings returns the number of entries in the string table.
func (*PrimitiveBlock) String ¶
func (pb *PrimitiveBlock) String(i int) []byte
String returns the string table entry at index i. The returned slice is a zero-copy reference into the block data and is only valid until the next call to DecodeFrom or Decompressor.Decompress. Panics if i is out of range.
type RelationBuf ¶
type RelationBuf struct {
Keys []uint32 // string table indices for tag keys
Vals []uint32 // string table indices for tag values
RolesSID []int32 // string table indices for member roles
MemIDs []int64 // delta-decoded absolute member IDs
Types []int32 // member types: MemberTypeNode, MemberTypeWay, MemberTypeRelation
}
RelationBuf is caller-managed memory for decoding Relation entities. Reuse across calls to avoid per-relation allocations. Keys, Vals, RolesSID, MemIDs, and Types are parallel arrays. MemIDs contains delta-decoded absolute member IDs.
type RelationScanner ¶
type RelationScanner struct {
// contains filtered or unexported fields
}
RelationScanner iterates over Relation messages in a PrimitiveGroup. Obtain one via GroupScanner.RelationScanner. RelationScanner is a value type.
func (*RelationScanner) Err ¶
func (rs *RelationScanner) Err() error
Err returns the first error encountered during iteration.
func (*RelationScanner) Next ¶
func (rs *RelationScanner) Next(buf *RelationBuf, info *InfoBuf) (id int64, ok bool)
Next decodes the next Relation into buf and returns its ID. Resets buf slices to [:0] then appends (capacity preserved). Returns (0, false) when no more relations remain. Pass a non-nil info to also decode the Relation's Info; nil skips it.
type WayBuf ¶
type WayBuf struct {
Keys []uint32 // string table indices for tag keys
Vals []uint32 // string table indices for tag values
Refs []int64 // delta-decoded absolute referenced node IDs
}
WayBuf is caller-managed memory for decoding Way entities. Reuse across calls to avoid per-way allocations. After DecodeWay, Keys and Vals are parallel string-table index arrays. Refs contains delta-decoded absolute node IDs.
type WayScanner ¶
type WayScanner struct {
// contains filtered or unexported fields
}
WayScanner iterates over Way messages in a PrimitiveGroup. Obtain one via GroupScanner.WayScanner. WayScanner is a value type.
func (*WayScanner) Err ¶
func (ws *WayScanner) Err() error
Err returns the first error encountered during iteration.
func (*WayScanner) Next ¶
func (ws *WayScanner) Next(buf *WayBuf, info *InfoBuf) (id int64, ok bool)
Next decodes the next Way into buf and returns its ID. Resets buf slices to [:0] then appends (capacity preserved). Returns (0, false) when no more ways remain. Pass a non-nil info to also decode the Way's Info; nil skips it.