Class: RTree

Inherits:
RTreeBase
  • Object
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

Defined Under Namespace

Classes: Style

Constant Summary

Constants inherited from RTreeBase

RTreeBase::AXIS_HEIGHT, RTreeBase::AXIS_WIDTH, RTreeBase::SPLIT_GREENE, RTreeBase::SPLIT_LINEAR, RTreeBase::SPLIT_QUADRATIC

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, :greene) (defaults to: :quadratic)

    determines the splitting strategy, the linear strategy is faster to build, the quadratic and greene strategies produce better-quality R-trees which are 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.


281
282
283
284
285
# File 'lib/rtree.rb', line 281

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:


169
170
171
# File 'lib/rtree.rb', line 169

def bsrt_read(io)
  super
end

.bugreportString

Returns email address for librtree bug reports.

Returns:

  • (String)

    email address for librtree bug reports


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

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, :greene) (defaults to: :quadratic)
  • node_page (Integer) (defaults to: 0)

Returns:

  • (RTree)

    the newly built RTree


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

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:


179
180
181
# File 'lib/rtree.rb', line 179

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


206
207
208
209
210
# File 'lib/rtree.rb', line 206

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:


157
158
159
# File 'lib/rtree.rb', line 157

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:


148
149
150
# File 'lib/rtree.rb', line 148

def json_read(io)
  super
end

.urlString

Returns librtree homepage.

Returns:

  • (String)

    librtree homepage


226
227
228
# File 'lib/rtree.rb', line 226

def url
  super
end

.versionArray<Integer>

Returns version of librtree.

Returns:

  • (Array<Integer>)

    version of librtree


214
215
216
# File 'lib/rtree.rb', line 214

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)

307
308
309
# File 'lib/rtree.rb', line 307

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


455
456
457
# File 'lib/rtree.rb', line 455

def branch_size
  super
end

#branching_factorInteger

Returns the number of branches from each node.

Returns:

  • (Integer)

    the number of branches from each node


461
462
463
# File 'lib/rtree.rb', line 461

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:


379
380
381
# File 'lib/rtree.rb', line 379

def bsrt_write(io)
  super
end

#cloneRTree

Create a deep copy

Returns:

  • (RTree)

    a deep copy of the RTree


290
291
292
# File 'lib/rtree.rb', line 290

def clone
  super
end

#dimInteger

Returns the dimension of the R-tree.

Returns:

  • (Integer)

    the dimension of the R-tree


420
421
422
# File 'lib/rtree.rb', line 420

def dim
  super
end

#empty?Boolean

Whether the RTree has any rectangles or not

Returns:

  • (Boolean)

    true if the RTree is empty


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

def empty?
  height.zero?
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


412
413
414
# File 'lib/rtree.rb', line 412

def eq?(other)
  super
end

#heightInteger

The height to the tree in the usual mathematical sense

Returns:

  • (Integer)

    the tree height


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

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:


368
369
370
# File 'lib/rtree.rb', line 368

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


443
444
445
# File 'lib/rtree.rb', line 443

def node_size
  super
end

#page_sizeInteger

Returns the bytes in a page of memory.

Returns:

  • (Integer)

    the bytes in a page of memory


437
438
439
# File 'lib/rtree.rb', line 437

def page_size
  super
end

#postscript(io, style, height: nil, width: nil, margin: 0) ⇒ Object

Create a PostScript plot of the RTree

Parameters:

  • io (IO)

    a writeable stream object

  • style (RTree::Style)

    a style object describing the fill colour and stroke width and colour for each level of the tree.

  • height (Float) (defaults to: nil)

    the height of the plot in units of PostScript point (1/72 inch)

  • width (Float) (defaults to: nil)

    the width of the plot in units of PostScript point (1/72 inch), if neither height nor width is given then a width of 216 (3 inches) will be taken as default

  • margin (Float) (defaults to: 0)

    extra space around the plot in units of PostScript point (1/72 inch), default zero


487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/rtree.rb', line 487

def postscript(io, style, height: nil, width: nil, margin: 0)
  if height && width then
    raise ArgumentError, 'cannot specify both height and width'
  end
  if height then
    axis = AXIS_HEIGHT
    extent = height
  else
    axis = AXIS_WIDTH
    extent = width || 216
  end
  super(style, axis, extent, margin, io)
end

#rect_sizeInteger

Returns the size in bytes of a rectangle.

Returns:

  • (Integer)

    the size in bytes of a rectangle


449
450
451
# File 'lib/rtree.rb', line 449

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).


317
318
319
320
321
322
323
324
325
# File 'lib/rtree.rb', line 317

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


431
432
433
# File 'lib/rtree.rb', line 431

def size
  super
end

#to_bsrtString

Serialise to BSRT string

Returns:

  • (String)

    the binary encoded BSRT

See Also:


395
396
397
# File 'lib/rtree.rb', line 395

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

#to_hHash

The RTree structure in hash form

Returns:

  • (Hash)

402
403
404
# File 'lib/rtree.rb', line 402

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:


387
388
389
# File 'lib/rtree.rb', line 387

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


468
469
470
# File 'lib/rtree.rb', line 468

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)

343
344
345
# File 'lib/rtree.rb', line 343

def update!
  super
end