Class: RTree

Inherits:
RTreeC show all
Defined in:
lib/rtree.rb

Overview

A Ruby wrapper around librtree implementing the R-tree spatial index of Guttman-Green.

Use

require 'rtree'

to make the RTree class available.

Given a set or rectangles (or higher dimensional cuboids) and their associated ids, one can build an RTree using the RTree.csv_read class method or repeated calls to the #add_rect instance method. The former is more performant since it avoids routing the rectangles through the Ruby-C interface.

Once the RTree instance is created one can make spatial queries against it with the #search method; one passes a rectangle to the method and it returns the ids of all of the input rectangles which intersect it (or yields them one at a time to a block, if given).

It may be convenient to serialise the RTree for faster loading, the library implements a custom binary format, BSRT (binary serialised R-tree) which can be written by #bsrt_write and read with RTree.bsrt_read. One can also serialise to JSON, but this results in a much larger file (a factor of ten) and so correspondingly slow to read/write. Useful, nevertheless, for debugging and loading into a native Ruby hash format (see #to_h).

All rectangles used in the library are simply arrays of floats, the lower value for each dimension, followed by the upper value for each dimension. Thus

[0, 0, 1, 2]

is the rectangle with 0 ≤ x ≤ 1 and 0 ≤ y ≤ 2. Upper and lower values may coincide (to create a line segment in 2-space, for example) but a lower value larger than the upper value will cause an error.

It is anticipated that the ids passed to the library will be used as an index for application specific data to which this rectangle relates (its location in an array, or a DB id) but this is entirely at the discretion of the caller, the library makes no use of the value, treating it as payload. In particular, the value may be non-unique and may be zero. One should note that the id type used internally by the library is determined at compile-time (with the RTREE_ID_TYPE variable) and by default this is a 64-bit unsigned integer.

Author:

  • RTree J. J. Green

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dim, split: :quadratic, node_page: 0) ⇒ RTree

Initialize a new (empty) RTree

Parameters:

  • dim (Integer)

    the dimension of the tree

  • split (:linear, :quadratic) (defaults to: :quadratic)

    determines the splitting strategy, the linear strategy is faster to build, the quadratic produces a better-quality R-tree which is faster to query.

  • node_page (Integer) (defaults to: 0)

    the nodes-per-page value. This value can affect performance quite dramatically, particularly build time. A value which is too large would result in an infeasible branching factor for the R-tree and will case the function to error with errno set to EINVAL. A value of zero is permitted and the default; in this case the function will choose a good value based on heuristics. You may get better performance for your use-case by manual experimentation, but zero is a good place to start.



195
196
197
198
199
# File 'lib/rtree.rb', line 195

def initialize(dim, split: :quadratic, node_page: 0)
  @split = split
  @node_page = node_page
  super(dim, flags)
end

Class Method Details

.bsrt_read(io) ⇒ RTree

Create a new RTree instance from BSRT (binary serialised R-tree) stream

Examples:

Read from file

rtree = File.open('rtree.bsrt', 'r') { |io| RTree.bsrt_read(io) }

Parameters:

  • io (IO)

    a readable stream object

Returns:

  • (RTree)

    the newly instantiated RTree

See Also:



83
84
85
# File 'lib/rtree.rb', line 83

def bsrt_read(io)
  super
end

.bugreportString

Returns email address for librtree bug reports.

Returns:

  • (String)

    email address for librtree bug reports



129
130
131
# File 'lib/rtree.rb', line 129

def bugreport
  super
end

.csv_read(io, dim, split: :quadratic, node_page: 0) ⇒ RTree

Note:

The CSV file (without header) should have the id in the first column, then twice as many floats as the dimension. Extra columns may be present and will be ignored (this useful feature is the reason that the dimension is a required argument).

Build a new RTree instance from CSV stream

Parameters:

  • io (IO)

    a readable stream object

  • dim (Integer)

    the dimension of the tree

  • split (:linear, :quadratic) (defaults to: :quadratic)
  • node_page (Integer) (defaults to: 0)

Returns:

  • (RTree)

    the newly built RTree



107
108
109
110
# File 'lib/rtree.rb', line 107

def csv_read(io, dim, split: :quadratic, node_page: 0)
  flags = split_flag(split) | node_page_flag(node_page)
  super(io, dim, flags)
end

.from_bsrt(bsrt) ⇒ RTree

Create a new RTree instance from BSRT (binary serialised R-tree) string

Parameters:

  • bsrt (String)

    a binary encoded string

Returns:

  • (RTree)

    the newly instantiated RTree

See Also:



92
93
94
# File 'lib/rtree.rb', line 92

def from_bsrt(bsrt)
  deserialise(bsrt, Encoding::BINARY) { |io| bsrt_read(io) }
end

.from_csv(csv, dim, **kwarg) ⇒ RTree

Build a new RTree instance from CSV string

Parameters:

  • csv (String)

    the CSV data in a string

  • dim (Integer)

    the dimension of the tree

  • kwarg (Hash)

    As for csv_read

Returns:

  • (RTree)

    the newly built RTree



117
118
119
120
121
# File 'lib/rtree.rb', line 117

def from_csv(csv, dim, **kwarg)
  deserialise(csv, Encoding::UTF_8) do |io|
    csv_read(io, dim, **kwarg)
  end
end

.from_json(json) ⇒ RTree

Create a new RTree instance from JSON string

Parameters:

  • json (String)

    a JSON string

Returns:

  • (RTree)

    the newly instantiated RTree

See Also:



72
73
74
# File 'lib/rtree.rb', line 72

def from_json(json)
  deserialise(json, Encoding::UTF_8) { |io| json_read(io) }
end

.json_read(io) ⇒ RTree

Create a new RTree instance from JSON stream

Examples:

Read from file

rtree = File.open('rtree.json', 'r') { |io| RTree.json_read(io) }

Parameters:

  • io (IO)

    a readable stream object

Returns:

  • (RTree)

    the newly instantiated RTree

See Also:



64
65
66
# File 'lib/rtree.rb', line 64

def json_read(io)
  super
end

.urlString

Returns librtree homepage.

Returns:

  • (String)

    librtree homepage



134
135
136
# File 'lib/rtree.rb', line 134

def url
  super
end

.versionArray<Integer>

Returns version of librtree.

Returns:

  • (Array<Integer>)

    version of librtree



124
125
126
# File 'lib/rtree.rb', line 124

def version
  @version ||= super.split('.').map(&:to_i)
end

Instance Method Details

#add_rect(id, coords) ⇒ self

Add a rectangle to the RTree

Examples:

Add a rectangle to a dimension two RTree

rtree = RTree.new(2)
rtree.add_rect(7, [0, 0, 1, 1])

Parameters:

  • id (Integer)

    the id of the rectangle. It is anticipated that the id will be used as an index for application specific data to which this rectangle relates, but this is entirely at the discretion of the caller, the library makes no use of the value, treating it as payload. In particular, the value may be non-unique and may be zero.

  • coords (Array<Float>)

    the extent of the rectangle, the minima for each dimension, then the maxima for each dimension.

Returns:

  • (self)


219
220
221
# File 'lib/rtree.rb', line 219

def add_rect(id, coords)
  super
end

#branch_sizeInteger

Returns the size in bytes of a branch.

Returns:

  • (Integer)

    the size in bytes of a branch



351
352
353
# File 'lib/rtree.rb', line 351

def branch_size
  super
end

#branching_factorInteger

Returns the number of branches from each node.

Returns:

  • (Integer)

    the number of branches from each node



356
357
358
# File 'lib/rtree.rb', line 356

def branching_factor
  super
end

#bsrt_write(io) ⇒ self

Serialise to BSRT (binary serialised R-tree) stream

Examples:

Write to file

File.open('rtree.bsrt', 'w') { |io| rtree.bsrt_write(io) }

Parameters:

  • io (IO)

    a writable stream

Returns:

  • (self)

See Also:



285
286
287
# File 'lib/rtree.rb', line 285

def bsrt_write(io)
  super
end

#cloneRTree

Create a deep copy

Returns:

  • (RTree)

    a deep copy of the RTree



203
204
205
# File 'lib/rtree.rb', line 203

def clone
  super
end

#dimInteger

Returns the dimension of the R-tree.

Returns:

  • (Integer)

    the dimension of the R-tree



321
322
323
# File 'lib/rtree.rb', line 321

def dim
  super
end

#empty?Boolean

Whether the RTree has any rectangles or not

Returns:

  • (Boolean)

    true if the RTree is empty



265
266
267
# File 'lib/rtree.rb', line 265

def empty?
  height == 0
end

#eq?(other) ⇒ Boolean Also known as: ==

Equality of RTrees. This is a rather strict equality, not only must the tree have the same rectangles, they must be in the same order. Certainly #clone will produce an instance which is equal in this sense.

Returns:

  • (Boolean)

    true if the instances are identical



314
315
316
# File 'lib/rtree.rb', line 314

def eq?(other)
  super
end

#heightInteger

The height to the tree in the usual mathematical sense

Returns:

  • (Integer)

    the tree height



259
260
261
# File 'lib/rtree.rb', line 259

def height
  super
end

#json_write(io) ⇒ self

Serialise to JSON stream

Examples:

Write to file

File.open('rtree.json', 'w') { |io| rtree.json_write(io) }

Parameters:

  • io (IO)

    a writable stream

Returns:

  • (self)

See Also:



275
276
277
# File 'lib/rtree.rb', line 275

def json_write(io)
  super
end

#node_sizeInteger

Returns the size in bytes of a node.

Returns:

  • (Integer)

    the size in bytes of a node



341
342
343
# File 'lib/rtree.rb', line 341

def node_size
  super
end

#page_sizeInteger

Returns the bytes in a page of memory.

Returns:

  • (Integer)

    the bytes in a page of memory



336
337
338
# File 'lib/rtree.rb', line 336

def page_size
  super
end

#rect_sizeInteger

Returns the size in bytes of a rectangle.

Returns:

  • (Integer)

    the size in bytes of a rectangle



346
347
348
# File 'lib/rtree.rb', line 346

def rect_size
  super
end

#search(coords) ⇒ Array<Integer>

Search the RTree

Parameters:

  • coords (Array<Float>)

    the search rectangle, as #add_rect

Returns:

  • (Array<Integer>)

    the ids of all rectangles which intersect the search rectangle. If a block is given then these values will be yielded to the block (one at a time).



228
229
230
231
232
233
234
235
236
# File 'lib/rtree.rb', line 228

def search(coords)
  if block_given? then
    super
  else
    ids = []
    super(coords) { |id| ids << id }
    ids
  end
end

#sizeInteger

Note:

This method traverses the entire tree summing the contributions for each node (rather than maintaining a running count). Performance-minded users may wish to cache this value (invalidating the cache when calling #add_rect of course).

Returns the total bytes allocated for the instance.

Returns:

  • (Integer)

    the total bytes allocated for the instance



331
332
333
# File 'lib/rtree.rb', line 331

def size
  super
end

#to_bsrtString

Serialise to BSRT string

Returns:

  • (String)

    the binary encoded BSRT

See Also:



299
300
301
# File 'lib/rtree.rb', line 299

def to_bsrt
  serialise(Encoding::BINARY) { |io| bsrt_write(io) }
end

#to_hHash

The RTree structure in hash form

Returns:

  • (Hash)


305
306
307
# File 'lib/rtree.rb', line 305

def to_h
  JSON.parse(to_json, symbolize_names: true)
end

#to_jsonString

Serialise to JSON string

Returns:

  • (String)

    the UTF-8 encoded JSON

See Also:



292
293
294
# File 'lib/rtree.rb', line 292

def to_json
  serialise(Encoding::UTF_8) { |io| json_write(io) }
end

#unit_sphere_volumeFloat

Returns the volume of the unit sphere in the R-tree’s dimension.

Returns:

  • (Float)

    the volume of the unit sphere in the R-tree’s dimension



362
363
364
# File 'lib/rtree.rb', line 362

def unit_sphere_volume
  super
end

#update! {|id, coords| ... } ⇒ self

Note:

In the C library, the implementation (via a C callback function) is much faster than rebuilding the R-tree; in this Ruby interface the callback must convert C floats to Ruby, yield them to the block and then convert the returned Ruby Floats to C; so we would expect that it loses much of its competitive advantage when compared to an R-tree rebuild.

Update the RTree. Modifies the rectangles in-place without changing the tree structure. Provided that the changes are small, the search efficiency should be close to that of freshly built RTree.

Examples:

Shift all rectangles by ε

rtree.update! { |id, coords| coords.map { |x| x + 

Yield Parameters:

Yield Returns:

  • (Array<Float>)

    the modified rectangle extent

Returns:

  • (self)


253
254
255
# File 'lib/rtree.rb', line 253

def update!
  super
end