Documentation ¶
Overview ¶
Package packedrtree provides a packed Hilbert R-Tree spatial index.
Although designed for FlatGeobuf compatibility, the simple, reusable, constructs within this package can be used standalone from FlatGeobuf, wherever a spatial index is needed.
Index ¶
Examples ¶
Constants ¶
const ( // HilbertOrder is the order of the Hilbert curve used in // HilbertSort. HilbertOrder = 16 )
Variables ¶
var EmptyBox = Box{ XMin: math.Inf(1), YMin: math.Inf(1), XMax: math.Inf(-1), YMax: math.Inf(-1), }
EmptyBox is an empty Box that can always be expanded.
Functions ¶
func HilbertSort ¶
HilbertSort sorts a list of feature references, whose overall bounding box is given by bounds, in descending order of position on a Hilbert curve of order HilbertOrder.
The sort algorithm is not guaranteed to be stable, so the relative position of two feature references with the same index on the Hilbert curve may change as a result of the sort.
The descending sort order may be somewhat unexpected, as ascending order is perhaps more idiomatic. Descending order is done to maximize consistency with other FlatGeobuf implementations, but nothing turns on the sort order. Both ascending and descending sorts can be used to create a valid PackedRTree, and any FlatGeobuf implementation will work equally well with an index sorted either way.
Example ¶
package main import ( "fmt" "github.com/gogama/flatgeobuf/packedrtree" ) // Create a Ref slice for example purposes. var refs = []packedrtree.Ref{ {Box: packedrtree.Box{XMin: -2, YMin: -2, XMax: -1, YMax: -1}, Offset: 0}, {Box: packedrtree.Box{XMin: 1, YMin: 1, XMax: 2, YMax: 2}, Offset: 1}, {Box: packedrtree.Box{XMin: -2, YMin: 1, XMax: -1, YMax: 2}, Offset: 2}, {Box: packedrtree.Box{XMin: 1, YMin: -2, XMax: 2, YMax: -1}, Offset: 3}, } func refsBounds(r []packedrtree.Ref) packedrtree.Box { b := packedrtree.EmptyBox for i := range r { b.Expand(&r[i].Box) } return b } func main() { packedrtree.HilbertSort(refs, refsBounds(refs)) fmt.Println(refs) }
Output: [Ref{[1,-2,2,-1],Offset:3} Ref{[1,1,2,2],Offset:1} Ref{[-2,1,-1,2],Offset:2} Ref{[-2,-2,-1,-1],Offset:0}]
Types ¶
type Box ¶
Box is a 2D bounding box.
func (*Box) Expand ¶
Expand ensures one Box completely contains another Box.
Expand makes the minimum necessary expansion to the receiver Box, and only if a change is necessary to contain the parameter.
func (*Box) ExpandXY ¶
ExpandXY ensures a Box contains a coordinate pair.
ExpandXY makes the minimum possible expansion to the receiver Box, and only if a change is necessary to contain the (x, y) coordinate.
type PackedRTree ¶
type PackedRTree struct {
// contains filtered or unexported fields
}
PackedRTree is a packed Hilbert R-Tree.
func New ¶
func New(refs []Ref, nodeSize uint16) (*PackedRTree, error)
New creates a new packed Hilbert R-Tree from a non-empty, Hilbert-sorted list of feature references and a given R-Tree node size. Panics if the reference list is empty or node size is less than 2.
Use HilbertSort to sort the feature references. If the input slice is not Hilbert-sorted, the behavior of the new PackedRTree is undefined.
Example ¶
package main import ( "fmt" "github.com/gogama/flatgeobuf/packedrtree" ) // Create a Ref slice for example purposes. var refs = []packedrtree.Ref{ {Box: packedrtree.Box{XMin: -2, YMin: -2, XMax: -1, YMax: -1}, Offset: 0}, {Box: packedrtree.Box{XMin: 1, YMin: 1, XMax: 2, YMax: 2}, Offset: 1}, {Box: packedrtree.Box{XMin: -2, YMin: 1, XMax: -1, YMax: 2}, Offset: 2}, {Box: packedrtree.Box{XMin: 1, YMin: -2, XMax: 2, YMax: -1}, Offset: 3}, } func refsBounds(r []packedrtree.Ref) packedrtree.Box { b := packedrtree.EmptyBox for i := range r { b.Expand(&r[i].Box) } return b } func main() { packedrtree.HilbertSort(refs, refsBounds(refs)) // Refs must be Hilbert-sorted for New. index, _ := packedrtree.New(refs, 10) // Ignore error ONLY to keep example simple. fmt.Println(index) }
Output: PackedRTree{Bounds:[-2,-2,2,2],NumRefs:4,NodeSize:10}
func Unmarshal ¶
Unmarshal deserializes a stream from the FlatGeobuf index section format, returning the in-memory search tree built from the stream.
If you are reading from a FlatGeobuf file, the reader should be positioned ready to read the first byte of the index section. If this function returns without error, the reader will be positioned ready to read the first byte of the data section.
The Seek function can be used to search an on-disk or in-storage representation of the index without needing to unmarshal it.
Example ¶
package main import ( "bytes" "fmt" "github.com/gogama/flatgeobuf/packedrtree" ) // Create a Ref slice for example purposes. var refs = []packedrtree.Ref{ {Box: packedrtree.Box{XMin: -2, YMin: -2, XMax: -1, YMax: -1}, Offset: 0}, {Box: packedrtree.Box{XMin: 1, YMin: 1, XMax: 2, YMax: 2}, Offset: 1}, {Box: packedrtree.Box{XMin: -2, YMin: 1, XMax: -1, YMax: 2}, Offset: 2}, {Box: packedrtree.Box{XMin: 1, YMin: -2, XMax: 2, YMax: -1}, Offset: 3}, } func refsBounds(r []packedrtree.Ref) packedrtree.Box { b := packedrtree.EmptyBox for i := range r { b.Expand(&r[i].Box) } return b } func main() { // Marshal an index to bytes so that we can Unmarshal it. packedrtree.HilbertSort(refs, refsBounds(refs)) // Refs must be Hilbert-sorted for New. index, _ := packedrtree.New(refs, 10) // Ignore error ONLY to keep example simple. var b bytes.Buffer _, _ = index.Marshal(&b) // Unmarshal from bytes. index, _ = packedrtree.Unmarshal(&b, len(refs), 10) fmt.Println(index) }
Output: PackedRTree{Bounds:[-2,-2,2,2],NumRefs:4,NodeSize:10}
func (*PackedRTree) Bounds ¶
func (prt *PackedRTree) Bounds() Box
Bounds returns the bounding box around all features referenced by the packed Hilbert R-Tree.
func (*PackedRTree) Marshal ¶
func (prt *PackedRTree) Marshal(w io.Writer) (n int, err error)
Marshal serializes the packed Hilbert R-Tree as a FlatGeobuf index section. It returns the number of bytes written.
If you are writing a complete FlatGeobuf file, the writer should be positioned ready to write the first byte of the index. If this method returns without error, the writer will be positioned ready to write the first byte of the data section.
func (*PackedRTree) NodeSize ¶
func (prt *PackedRTree) NodeSize() uint16
NodeSize returns the number of R-Tree child nodes per parent node.
func (*PackedRTree) NumRefs ¶
func (prt *PackedRTree) NumRefs() int
NumRefs returns the number of feature references stored in the packed Hilbert R-Tree.
func (*PackedRTree) Search ¶
func (prt *PackedRTree) Search(b Box) Results
Search searches the packed Hilbert R-Tree for qualified matches whose bounding rectangles intersect the query box. The order of the search results is not defined.
To directly search the index section of FlatGeobuf file without creating a PackedRTree, consider using the Seek function.
Example ¶
package main import ( "fmt" "github.com/gogama/flatgeobuf/packedrtree" ) // Create a Ref slice for example purposes. var refs = []packedrtree.Ref{ {Box: packedrtree.Box{XMin: -2, YMin: -2, XMax: -1, YMax: -1}, Offset: 0}, {Box: packedrtree.Box{XMin: 1, YMin: 1, XMax: 2, YMax: 2}, Offset: 1}, {Box: packedrtree.Box{XMin: -2, YMin: 1, XMax: -1, YMax: 2}, Offset: 2}, {Box: packedrtree.Box{XMin: 1, YMin: -2, XMax: 2, YMax: -1}, Offset: 3}, } func refsBounds(r []packedrtree.Ref) packedrtree.Box { b := packedrtree.EmptyBox for i := range r { b.Expand(&r[i].Box) } return b } func main() { packedrtree.HilbertSort(refs, refsBounds(refs)) // Refs must be Hilbert-sorted for New. index, _ := packedrtree.New(refs, 10) // Ignore error ONLY to keep example simple. rs1 := index.Search(packedrtree.EmptyBox) // Search 1 fmt.Println("Search 1:", rs1) rs2 := index.Search(packedrtree.Box{XMin: -10, YMin: -10, XMax: -5, YMax: -5}) // Search 2 fmt.Println("Search 2:", rs2) rs3 := index.Search(index.Bounds()) // Search 3 fmt.Printf("Search 3: %+v\n", rs3) rs4 := index.Search(packedrtree.Box{XMin: 0, YMin: -1, XMax: 1, YMax: 0}) // Search 4 fmt.Printf("Search 4: %+v\n", rs4) }
Output: Search 1: [] Search 2: [] Search 3: [{Offset:3 RefIndex:0} {Offset:1 RefIndex:1} {Offset:2 RefIndex:2} {Offset:0 RefIndex:3}] Search 4: [{Offset:3 RefIndex:0}]
func (*PackedRTree) String ¶
func (prt *PackedRTree) String() string
String returns a summary description of the packed Hilbert R-Tree.
type Ref ¶
type Ref struct { // The Box is the bounding box of the referenced feature. Box // Offset is the referenced feature's byte offset into the data // section, when working with FlatGeobuf; otherwise it can contain // any arbitrary data useful for identifying the referenced feature. Offset int64 }
A Ref is a single item within the PackedRTree and represents a reference to a feature stored elsewhere. That "elsewhere" will often be the data section of a FlatGeobuf file.
type Result ¶
type Result struct { // Offset contains the Offset field from the matched Ref. // // If working with FlatGeobuf, this is the byte offset of the result // feature in the FlatGeobuf data section. Offset int64 // RefIndex is the index of the matched Ref. // // If working with a PackedRTree created by new, this is the index // of the Ref in the input slice passed to New. If working with a // PackedRTree deserialized with Unmarshal, this is the index of // the Ref relative to the start of the leaf nodes in the serialized // representation. RefIndex int }
Result is a qualified index search result. It represents a matched Ref, i.e. a Ref whose bounding box intersected the query box.
type Results ¶
type Results []Result
Results is a slice of Result structures implementing sort.Interface. The sort.Sort function will sort a Results instance in ascending order of Result.Offset.
func Seek ¶
Seek searches the serialized representation of a packed Hilbert R-Tree index directly, from a seekable stream, without needing to Unmarshal the index into an in-memory data structure.
Seek returns all qualified matches whose bounding boxes intersect the query box. Results are guaranteed to be in ascending order of Result.Offset.
The seekable reader should be positioned ready to read the first byte of the FlatGeobuf index section. If this function returns without error, the seekable reader will be positioned ready to read the first byte of the data section.
Example ¶
package main import ( "bytes" "fmt" "github.com/gogama/flatgeobuf/packedrtree" ) // Create a Ref slice for example purposes. var refs = []packedrtree.Ref{ {Box: packedrtree.Box{XMin: -2, YMin: -2, XMax: -1, YMax: -1}, Offset: 0}, {Box: packedrtree.Box{XMin: 1, YMin: 1, XMax: 2, YMax: 2}, Offset: 1}, {Box: packedrtree.Box{XMin: -2, YMin: 1, XMax: -1, YMax: 2}, Offset: 2}, {Box: packedrtree.Box{XMin: 1, YMin: -2, XMax: 2, YMax: -1}, Offset: 3}, } func refsBounds(r []packedrtree.Ref) packedrtree.Box { b := packedrtree.EmptyBox for i := range r { b.Expand(&r[i].Box) } return b } func main() { // Marshal an index to bytes so that we can seek within the raw bytes. packedrtree.HilbertSort(refs, refsBounds(refs)) // Refs must be Hilbert-sorted for New. index, _ := packedrtree.New(refs, 10) // Ignore error ONLY to keep example simple. var b bytes.Buffer _, _ = index.Marshal(&b) // Do four streaming index searches on the raw index bytes. rs1, err1 := packedrtree.Seek(bytes.NewReader(b.Bytes()), len(refs), 10, packedrtree.EmptyBox) fmt.Println("Seek 1:", rs1, err1) rs2, err2 := packedrtree.Seek(bytes.NewReader(b.Bytes()), len(refs), 10, packedrtree.Box{XMin: -10, YMin: -10, XMax: -5, YMax: -5}) // Seek 2 fmt.Println("Seek 2:", rs2, err2) rs3, err3 := packedrtree.Seek(bytes.NewReader(b.Bytes()), len(refs), 10, index.Bounds()) // Seek 3 fmt.Printf("Seek 3: %+v %v\n", rs3, err3) rs4, err4 := packedrtree.Seek(bytes.NewReader(b.Bytes()), len(refs), 10, packedrtree.Box{XMin: 0, YMin: -1, XMax: 1, YMax: 0}) // Seek 4 fmt.Printf("Seek 4: %+v %v\n", rs4, err4) }
Output: Seek 1: [] <nil> Seek 2: [] <nil> Seek 3: [{Offset:3 RefIndex:0} {Offset:1 RefIndex:1} {Offset:2 RefIndex:2} {Offset:0 RefIndex:3}] <nil> Seek 4: [{Offset:3 RefIndex:0}] <nil>
func (Results) Len ¶
Len returns the length of the slice. It implements the corresponding method of sort.Interface.