librtree 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/ext/rtree/extconf.rb +31 -0
  3. data/ext/rtree/rtree.c +362 -0
  4. data/lib/rtree.rb +328 -0
  5. metadata +134 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 35028e6e74b4682479656b89086bf47fb42a43bd09ce7c9070df53adffcf4cec
4
+ data.tar.gz: d58480430dada176e90f0e42e316b82be990fc4c414a993ce627a1b4b11ba707
5
+ SHA512:
6
+ metadata.gz: 5bcb8f2b41ccab397f5accef4846912e9932dd07fa540c020c114a079ed9252296a8324ae647452170a445babcceb03984f258d053eb55d45566ad1b6da03d67
7
+ data.tar.gz: 8de9c0cbcfde749d139590c81d41b55bc78c73aa826cb5385f65e9b4a1c36e8a08abc3e1f5b751a082f33fadb3e56001dd8a391f93c6314f4728e5ec772454ab
@@ -0,0 +1,31 @@
1
+ require 'mkmf'
2
+
3
+ $CFLAGS << ' -std=c99'
4
+
5
+ INCLUDEDIR = RbConfig::CONFIG['includedir']
6
+ HEADER_DIRS = [
7
+ '/usr/local/include',
8
+ INCLUDEDIR,
9
+ '/usr/include',
10
+ ]
11
+
12
+ LIBDIR = RbConfig::CONFIG['libdir']
13
+ LIB_DIRS = [
14
+ '/usr/local/lib',
15
+ LIBDIR,
16
+ '/usr/lib'
17
+ ]
18
+
19
+ puts LIB_DIRS.inspect
20
+
21
+ dir_config('librtree', HEADER_DIRS, LIB_DIRS)
22
+
23
+ abort 'missing csv.h' unless find_header('csv.h')
24
+ abort 'missing jansson.h' unless find_header('jansson.h')
25
+ abort 'missing rtree.h' unless find_header('rtree.h')
26
+
27
+ abort "libcsv is missing" unless find_library('csv', 'csv_init')
28
+ abort "libjansson is missing" unless find_library('jansson', 'json_pack')
29
+ abort "librtree is missing" unless find_library('rtree', 'rtree_new')
30
+
31
+ create_makefile 'rtree/rtree'
data/ext/rtree/rtree.c ADDED
@@ -0,0 +1,362 @@
1
+ #include <ruby.h>
2
+ #include <ruby/io.h>
3
+
4
+ #include <rtree.h>
5
+
6
+ #include <errno.h>
7
+ #include <stdint.h>
8
+
9
+ typedef struct
10
+ {
11
+ void *rtree;
12
+ } lrt_t;
13
+
14
+ static void lrt_free(void *p)
15
+ {
16
+ lrt_t *lrt = p;
17
+
18
+ if (lrt->rtree)
19
+ {
20
+ rtree_destroy(lrt->rtree);
21
+ lrt->rtree = NULL;
22
+ }
23
+ }
24
+
25
+ static VALUE lrt_alloc(VALUE cls)
26
+ {
27
+ lrt_t *lrt;
28
+ VALUE obj = Data_Make_Struct(cls, lrt_t, NULL, lrt_free, lrt);
29
+
30
+ lrt->rtree = NULL;
31
+
32
+ return obj;
33
+ }
34
+
35
+ static VALUE lrt_init(VALUE self, VALUE dim_obj, VALUE flags_obj)
36
+ {
37
+ Check_Type(dim_obj, T_FIXNUM);
38
+ size_t dim = FIX2ULONG(dim_obj);
39
+
40
+ Check_Type(dim_obj, T_FIXNUM);
41
+ state_flags_t flags = FIX2UINT(flags_obj);
42
+
43
+ lrt_t *lrt;
44
+ Data_Get_Struct(self, lrt_t, lrt);
45
+
46
+ if ((lrt->rtree = rtree_new(dim, flags)) == NULL)
47
+ rb_raise(rb_eNoMemError, "failed to create rtree");
48
+
49
+ return self;
50
+ }
51
+
52
+ static VALUE lrt_release(VALUE self)
53
+ {
54
+ lrt_t *lrt;
55
+ Data_Get_Struct(self, lrt_t, lrt);
56
+
57
+ free(lrt->rtree);
58
+
59
+ return self;
60
+ }
61
+
62
+ static VALUE lrt_height(VALUE self)
63
+ {
64
+ lrt_t *lrt;
65
+ Data_Get_Struct(self, lrt_t, lrt);
66
+
67
+ return INT2NUM(rtree_height(lrt->rtree));
68
+ }
69
+
70
+ static VALUE lrt_add_rect(VALUE self, VALUE id_obj, VALUE coord_obj)
71
+ {
72
+ Check_Type(coord_obj, T_ARRAY);
73
+ Check_Type(id_obj, T_FIXNUM);
74
+
75
+ lrt_t *lrt;
76
+ Data_Get_Struct(self, lrt_t, lrt);
77
+
78
+ rtree_t
79
+ *rtree = lrt->rtree;
80
+ rtree_id_t
81
+ id = FIX2ULONG(id_obj);
82
+ size_t
83
+ len = RARRAY_LEN(coord_obj),
84
+ dim = state_dims(rtree->state);
85
+
86
+ if (len != 2 * dim)
87
+ rb_raise(rb_eArgError, "expected array length %zi, got %zi", 2 * dim, len);
88
+
89
+ rtree_coord_t coord[len];
90
+
91
+ for (size_t i = 0 ; i < len ; i++)
92
+ coord[i] = NUM2DBL(rb_ary_entry(coord_obj, i));
93
+
94
+ int err;
95
+
96
+ if ((err = rtree_add_rect(rtree, id, coord)) != 0)
97
+ {
98
+ if (errno)
99
+ rb_sys_fail("add_rect");
100
+ else
101
+ rb_raise(rb_eRuntimeError, "%s", rtree_strerror(err));
102
+ }
103
+
104
+ return self;
105
+ }
106
+
107
+ /* update rectangles in the tree without changing its structure */
108
+
109
+ static int update_cb(rtree_id_t id, rtree_coord_t *coord, void *context)
110
+ {
111
+ size_t len = *(size_t*)context;
112
+ VALUE id_obj = INT2NUM(id);
113
+ VALUE coord_obj = rb_ary_new2(len);
114
+
115
+ for (size_t i = 0 ; i < len ; i++)
116
+ rb_ary_store(coord_obj, i, DBL2NUM(coord[i]));
117
+
118
+ coord_obj = rb_yield_values(2, id_obj, coord_obj);
119
+
120
+ Check_Type(coord_obj, T_ARRAY);
121
+ if (len != (size_t)RARRAY_LEN(coord_obj))
122
+ rb_raise(rb_eArgError, "Array of wrong size");
123
+
124
+ for (size_t i = 0 ; i < len ; i++)
125
+ coord[i] = NUM2DBL(rb_ary_entry(coord_obj, i));
126
+
127
+ return 0;
128
+ }
129
+
130
+ static VALUE lrt_update(VALUE self)
131
+ {
132
+ if (!rb_block_given_p())
133
+ rb_raise(rb_eArgError, "Expected block");
134
+
135
+ lrt_t *lrt;
136
+ Data_Get_Struct(self, lrt_t, lrt);
137
+ rtree_t *rtree = lrt->rtree;
138
+ size_t len = 2 * state_dims(rtree->state);
139
+
140
+ int err = rtree_update(rtree, update_cb, &len);
141
+
142
+ if (err != 0)
143
+ rb_raise(rb_eRuntimeError, "librtree: %s", strerror(err));
144
+
145
+ return self;
146
+ }
147
+
148
+ /*
149
+ The librtree API expects the search callback to return zero to
150
+ continue the search, non-zero to terminate. I was expecting to
151
+ need to wrap the 'yield' in 'rescue's and 'ensure's to handle
152
+ exceptions and breaks, but find that doing nothing actually does
153
+ the right thing (see specs). The search implemetation is just
154
+ a recursive search through the tree (as you would expect) so
155
+ there is no end-of-search cleanup to do, so I think this is all
156
+ just fine ... wild
157
+ */
158
+
159
+ static int search_cb(rtree_id_t id, void *context)
160
+ {
161
+ rb_yield(INT2NUM(id));
162
+ return 0;
163
+ }
164
+
165
+ static VALUE lrt_search(VALUE self, VALUE coord_obj)
166
+ {
167
+ if (!rb_block_given_p())
168
+ rb_raise(rb_eArgError, "Expected block");
169
+
170
+ Check_Type(coord_obj, T_ARRAY);
171
+
172
+ lrt_t *lrt;
173
+ Data_Get_Struct(self, lrt_t, lrt);
174
+
175
+ rtree_t
176
+ *rtree = lrt->rtree;
177
+ size_t
178
+ len = RARRAY_LEN(coord_obj),
179
+ dim = state_dims(rtree->state);
180
+
181
+ if (len != 2 * dim)
182
+ rb_raise(rb_eArgError, "expected array length %zi, got %zi", 2 * dim, len);
183
+
184
+ rtree_coord_t coord[len];
185
+
186
+ for (size_t i = 0 ; i < len ; i++)
187
+ coord[i] = NUM2DBL(rb_ary_entry(coord_obj, i));
188
+
189
+ rtree_search(rtree, coord, search_cb, NULL);
190
+
191
+ return Qnil;
192
+ }
193
+
194
+ /* deserialisation */
195
+
196
+ typedef rtree_t* (deserialise_t)(FILE*);
197
+
198
+ static VALUE deserialise(VALUE cls, VALUE io_obj, deserialise_t *f)
199
+ {
200
+ Check_Type(io_obj, T_FILE);
201
+
202
+ rb_io_t *io;
203
+ GetOpenFile(io_obj, io);
204
+ rb_io_check_initialized(io);
205
+ rb_io_check_readable(io);
206
+ FILE *fp = rb_io_stdio_file(io);
207
+
208
+ rtree_t *rtree;
209
+ errno = 0;
210
+ if ((rtree = f(fp)) == NULL)
211
+ {
212
+ if (errno)
213
+ rb_sys_fail(__func__);
214
+ else
215
+ rb_raise(rb_eRuntimeError, "Failed read from stream");
216
+ }
217
+
218
+ lrt_t *lrt;
219
+ VALUE obj = Data_Make_Struct(cls, lrt_t, NULL, lrt_free, lrt);
220
+
221
+ lrt->rtree = rtree;
222
+
223
+ return obj;
224
+ }
225
+
226
+ static VALUE lrt_json_read(VALUE cls, VALUE io_obj)
227
+ {
228
+ return deserialise(cls, io_obj, rtree_json_read);
229
+ }
230
+
231
+ static VALUE lrt_bsrt_read(VALUE cls, VALUE io_obj)
232
+ {
233
+ return deserialise(cls, io_obj, rtree_bsrt_read);
234
+ }
235
+
236
+ static VALUE lrt_csv_read(VALUE cls,
237
+ VALUE io_obj, VALUE dim_obj, VALUE flags_obj)
238
+ {
239
+ Check_Type(io_obj, T_FILE);
240
+ rb_io_t *io;
241
+ GetOpenFile(io_obj, io);
242
+ rb_io_check_initialized(io);
243
+ rb_io_check_readable(io);
244
+ FILE *fp = rb_io_stdio_file(io);
245
+
246
+ Check_Type(dim_obj, T_FIXNUM);
247
+ size_t dim = FIX2ULONG(dim_obj);
248
+
249
+ Check_Type(dim_obj, T_FIXNUM);
250
+ state_flags_t flags = FIX2UINT(flags_obj);
251
+
252
+ rtree_t *rtree;
253
+ errno = 0;
254
+ if ((rtree = rtree_csv_read(fp, dim, flags)) == NULL)
255
+ {
256
+ if (errno)
257
+ rb_sys_fail(__func__);
258
+ else
259
+ rb_raise(rb_eRuntimeError, "Failed read from stream");
260
+ }
261
+
262
+ lrt_t *lrt;
263
+ VALUE obj = Data_Make_Struct(cls, lrt_t, NULL, lrt_free, lrt);
264
+
265
+ lrt->rtree = rtree;
266
+
267
+ return obj;
268
+ }
269
+
270
+ /* serialisation */
271
+
272
+ typedef int (serialise_t)(const rtree_t*, FILE*);
273
+
274
+ static VALUE serialise(VALUE self, VALUE io_obj, serialise_t *f)
275
+ {
276
+ Check_Type(io_obj, T_FILE);
277
+
278
+ rb_io_t *io;
279
+ GetOpenFile(io_obj, io);
280
+ rb_io_check_initialized(io);
281
+ rb_io_check_writable(io);
282
+ FILE *fp = rb_io_stdio_file(io);
283
+
284
+ lrt_t *lrt;
285
+ Data_Get_Struct(self, lrt_t, lrt);
286
+ rtree_t *rtree = lrt->rtree;
287
+
288
+ int err = f(rtree, fp);
289
+ if (err != 0)
290
+ rb_raise(rb_eRuntimeError, "librtree: %s", rtree_strerror(err));
291
+
292
+ return self;
293
+ }
294
+
295
+ static VALUE lrt_json_write(VALUE self, VALUE io_obj)
296
+ {
297
+ return serialise(self, io_obj, rtree_json_write);
298
+ }
299
+
300
+ static VALUE lrt_bsrt_write(VALUE self, VALUE io_obj)
301
+ {
302
+ return serialise(self, io_obj, rtree_bsrt_write);
303
+ }
304
+
305
+ static VALUE lrt_identical(VALUE self, VALUE other)
306
+ {
307
+ lrt_t *lrt_self, *lrt_other;
308
+ Data_Get_Struct(self, lrt_t, lrt_self);
309
+ Data_Get_Struct(other, lrt_t, lrt_other);
310
+ rtree_t
311
+ *rtree_self = lrt_self->rtree,
312
+ *rtree_other = lrt_other->rtree;
313
+
314
+ if (rtree_identical(rtree_self, rtree_other))
315
+ return Qtrue;
316
+ else
317
+ return Qfalse;
318
+ }
319
+
320
+ static VALUE lrt_clone(VALUE self)
321
+ {
322
+ lrt_t *lrt;
323
+ Data_Get_Struct(self, lrt_t, lrt);
324
+ rtree_t *rtree;
325
+
326
+ errno = 0;
327
+ if ((rtree = rtree_clone(lrt->rtree)) == NULL)
328
+ {
329
+ if (errno)
330
+ rb_sys_fail(__func__);
331
+ else
332
+ rb_raise(rb_eRuntimeError, "Failed clone");
333
+ }
334
+
335
+ lrt_t *lrt_cloned;
336
+ VALUE cls = CLASS_OF(self);
337
+ VALUE obj = Data_Make_Struct(cls, lrt_t, NULL, lrt_free, lrt_cloned);
338
+
339
+ lrt_cloned->rtree = rtree;
340
+
341
+ return obj;
342
+ }
343
+
344
+ void Init_rtree(void)
345
+ {
346
+ VALUE cls = rb_const_get(rb_cObject, rb_intern("RTreeC"));
347
+
348
+ rb_define_alloc_func(cls, lrt_alloc);
349
+ rb_define_method(cls, "initialize", lrt_init, 2);
350
+ rb_define_method(cls, "free", lrt_release, 0);
351
+ rb_define_method(cls, "clone", lrt_clone, 0);
352
+ rb_define_method(cls, "update!", lrt_update, 0);
353
+ rb_define_method(cls, "height", lrt_height, 0);
354
+ rb_define_method(cls, "add_rect", lrt_add_rect, 2);
355
+ rb_define_method(cls, "search", lrt_search, 1);
356
+ rb_define_method(cls, "json_write", lrt_json_write, 1);
357
+ rb_define_method(cls, "bsrt_write", lrt_bsrt_write, 1);
358
+ rb_define_method(cls, "eq?", lrt_identical, 1);
359
+ rb_define_singleton_method(cls, "json_read", lrt_json_read, 1);
360
+ rb_define_singleton_method(cls, "bsrt_read", lrt_bsrt_read, 1);
361
+ rb_define_singleton_method(cls, "csv_read", lrt_csv_read, 3);
362
+ }
data/lib/rtree.rb ADDED
@@ -0,0 +1,328 @@
1
+ # The C extension
2
+ class RTreeC ; end
3
+
4
+ # @author RTree J. J. Green
5
+ #
6
+ # A Ruby wrapper around
7
+ # {http://soliton.vm.bytemark.co.uk/pub/jjg/en/code/librtree librtree}
8
+ # implementing the R-tree spatial index of Guttman-Green.
9
+ #
10
+ # Given a set or rectangles (or higher dimensional cuboids) and their
11
+ # associated ids, one can build an {RTree} using the {.csv_read} class
12
+ # method or repeated calls to the {#add_rect} instance method. The
13
+ # former is more performant since it avoids routing the rectangles
14
+ # through the Ruby-C interface.
15
+ #
16
+ # Once the {RTree} instance is created one can make spatial queries
17
+ # against it with the {#search} method; one passes a rectangle to the
18
+ # method and it returns the ids of all of the input rectangles which
19
+ # intersect it (or yields them one at a time to a block, if given).
20
+ #
21
+ # It may be convenient to serialise the {RTree} for faster loading, the
22
+ # library implements a custom binary format, BSRT (binary serialised
23
+ # R-tree) which can be written by {#bsrt_write} and read with {.bsrt_read}.
24
+ # One can also serialise to JSON, but this results in a much larger file
25
+ # (a factor of ten) and so correspondingly slow to read/write. Useful,
26
+ # nevertheless, for debugging and loading into a native Ruby hash format
27
+ # (see {#to_h}).
28
+ #
29
+ # All rectangles used in the library are simply arrays of floats, the
30
+ # lower value for each dimension, followed by the upper value for each
31
+ # dimension. Thus
32
+ #
33
+ # [0, 0, 1, 2]
34
+ #
35
+ # is the rectangle with 0 ≤ _x_ ≤ 1 and 0 ≤ _y_ ≤ 2. Upper and lower
36
+ # values may coincide (to create a line segment in 2-space, for example)
37
+ # but a lower value larger than the upper value will cause an error.
38
+ #
39
+ # It is anticipated that the ids passed to the library will be used as
40
+ # an index for application specific data to which this rectangle relates
41
+ # (its location in an array, or a DB id) but this is entirely at the
42
+ # discretion of the caller, the library makes no use of the value, treating
43
+ # it as payload. In particular, the value may be non-unique and may be zero.
44
+ # One should note that the id type used internally by the library is
45
+ # determined at compile-time (with the RTREE_ID_TYPE variable) and by
46
+ # default this is a 32-bit unsigned integer.
47
+ #
48
+ class RTree < RTreeC
49
+
50
+ class << self
51
+
52
+ # Create a new RTree instance from JSON stream
53
+ # @param io [IO] a readable stream object
54
+ # @return [RTree] the newly instantiated RTree
55
+ # @see #json_write
56
+ # @example Read from file
57
+ # rtree = File.open('rtree.json', 'r') { |io| RTree.json_read(io) }
58
+ def json_read(io)
59
+ super
60
+ end
61
+
62
+ # Create a new RTree instance from JSON string
63
+ # @param json [String] a JSON string
64
+ # @return [RTree] the newly instantiated RTree
65
+ # @see #to_json
66
+ def from_json(json)
67
+ deserialise(json, Encoding::UTF_8) { |io| json_read(io) }
68
+ end
69
+
70
+ # Create a new RTree instance from BSRT (binary serialised R-tree)
71
+ # stream
72
+ # @param io [IO] a readable stream object
73
+ # @return [RTree] the newly instantiated RTree
74
+ # @see #bsrt_write
75
+ # @example Read from file
76
+ # rtree = File.open('rtree.bsrt', 'r') { |io| RTree.bsrt_read(io) }
77
+ def bsrt_read(io)
78
+ super
79
+ end
80
+
81
+ # Create a new RTree instance from BSRT (binary serialised R-tree)
82
+ # string
83
+ # @param bsrt [String] a binary encoded string
84
+ # @return [RTree] the newly instantiated RTree
85
+ # @see #to_bsrt
86
+ def from_bsrt(bsrt)
87
+ deserialise(bsrt, Encoding::BINARY) { |io| bsrt_read(io) }
88
+ end
89
+
90
+ # Build a new RTree instance from CSV stream
91
+ # @param io [IO] a readable stream object
92
+ # @param dim [Integer] the dimension of the tree
93
+ # @param split [:linear, :quadratic] See {#initialize}
94
+ # @param node_page [Integer] See {#initialize}
95
+ # @return [RTree] the newly built RTree
96
+ def csv_read(io, dim, split: :quadratic, node_page: 0)
97
+ flags = split_flag(split) | node_page_flag(node_page)
98
+ super(io, dim, flags)
99
+ end
100
+
101
+ # Build a new RTree instance from CSV string
102
+ # @param csv [String] the CSV data in a string
103
+ # @param dim [Integer] the dimension of the tree
104
+ # @param kwarg [Hash] As for {.csv_read}
105
+ # @return [RTree] the newly built RTree
106
+ def from_csv(csv, dim, **kwarg)
107
+ deserialise(csv, Encoding::UTF_8) do |io|
108
+ csv_read(io, dim, **kwarg)
109
+ end
110
+ end
111
+
112
+ # @!visibility private
113
+ def split_flag(split)
114
+ case split
115
+ when :quadratic
116
+ 0
117
+ when :linear
118
+ 1
119
+ else
120
+ raise ArgumentError, "bad split value: #{split}"
121
+ end
122
+ end
123
+
124
+ # @!visibility private
125
+ def node_page_flag(node_page)
126
+ node_page << 2
127
+ end
128
+
129
+ private
130
+
131
+ def deserialise(string, encoding)
132
+ raise TypeError unless string.is_a? String
133
+ rd, wr = IO.pipe(encoding)
134
+ if fork then
135
+ wr.close
136
+ begin
137
+ result = yield(rd)
138
+ ensure
139
+ rd.close
140
+ Process.wait
141
+ end
142
+ else
143
+ rd.close
144
+ begin
145
+ wr.write(string)
146
+ ensure
147
+ wr.close
148
+ exit!
149
+ end
150
+ end
151
+ result
152
+ end
153
+ end
154
+
155
+ # Initialize a new (empty) RTree
156
+ # @param dim [Integer] the dimension of the tree
157
+ # @param split [:linear, :quadratic] determines the splitting strategy,
158
+ # the linear strategy is faster to build, the quadratic produces a
159
+ # better-quality R-tree which is faster to query.
160
+ # @param node_page [Integer] the nodes-per-page value. This value can
161
+ # affect performance quite dramatically, particularly build time. A
162
+ # value which is too large would result in an infeasible branching
163
+ # factor for the R-tree and will case the function to error with errno
164
+ # set to EINVAL. A value of zero is permitted and the default; in
165
+ # this case the function will choose a good value based on heuristics.
166
+ # You may get better performance for your use-case by manual
167
+ # experimentation, but zero is a good place to start.
168
+ # @return [RTree] the newly instantiated RTree
169
+ def initialize(dim, split: :quadratic, node_page: 0)
170
+ @split = split
171
+ @node_page = node_page
172
+ super(dim, flags)
173
+ end
174
+
175
+ # Create a deep copy
176
+ # @return [RTree] a deep copy of the RTree
177
+ def clone
178
+ super
179
+ end
180
+
181
+ # Add a rectangle to the RTree
182
+ # @param id [Integer] the id of the rectangle. It is anticipated that
183
+ # the id will be used as an index for application specific data to
184
+ # which this rectangle relates, but this is entirely at the discretion
185
+ # of the caller, the library makes no use of the value, treating it as
186
+ # payload. In particular, the value may be non-unique and may be zero.
187
+ # @param coords [Array<Float>] the extent of the rectangle, the minima
188
+ # for each dimension, then the maxima for each dimension.
189
+ # @return [self]
190
+ # @example Add a rectangle to a dimension two RTree
191
+ # rtree = RTree.new(2)
192
+ # rtree.add_rect(7, [0, 0, 1, 1])
193
+ def add_rect(id, coords)
194
+ super
195
+ end
196
+
197
+ # Search the RTree
198
+ # @param coords [Array<Float>] the search rectangle, as {#add_rect}
199
+ # @return [Array<Integer>] the ids of all rectangles which intersect
200
+ # the search rectangle. If a block is given then these values will
201
+ # be yielded to the block (one at a time).
202
+ def search(coords)
203
+ if block_given? then
204
+ super
205
+ else
206
+ ids = []
207
+ super(coords) { |id| ids << id }
208
+ ids
209
+ end
210
+ end
211
+
212
+ # Update the RTree. Modifies the rectangles in-place without changing
213
+ # the tree structure. Provided that the changes are small, the search
214
+ # efficiency should be close to that of freshly built RTree.
215
+ # @yieldparam id [Integer] see {#add_rect}
216
+ # @yieldparam coords [Array<Float>] see {#add_rect}
217
+ # @yieldreturn [Array<Float>] the modified rectangle extent
218
+ # @return [self]
219
+ # @example Shift all rectangles by ε
220
+ # rtree.update! { |id, coords| coords.map { |x| x + ε } }
221
+ # @note In the C library, the implementation (via a C callback function)
222
+ # is much faster than rebuilding the R-tree; in this Ruby interface
223
+ # the callback must convert C floats to Ruby, yield them to the
224
+ # block and then convert the returned Ruby Floats to C; so we would
225
+ # expect that it loses much of its competetive advantage when
226
+ # compared to an R-tree rebuild.
227
+ def update!
228
+ super
229
+ end
230
+
231
+ # The height to the tree in the usual mathematical sense
232
+ # @return [Integer] the tree height
233
+ def height
234
+ super
235
+ end
236
+
237
+ # Whether the RTree has any rectangles or not
238
+ # @return [Boolean] true if the RTree is empty
239
+ def empty?
240
+ height == 0
241
+ end
242
+
243
+ # Serialise to JSON stream
244
+ # @param io [IO] a writeable stream
245
+ # @return [self]
246
+ # @see .json_read
247
+ # @example Write to file
248
+ # File.open('rtree.json', 'w') { |io| rtree.json_write(io) }
249
+ def json_write(io)
250
+ super
251
+ end
252
+
253
+ # Serialise to BSRT (binary serialised R-tree) stream
254
+ # @param io [IO] a writeable stream
255
+ # @return [self]
256
+ # @see .bsrt_read
257
+ # @example Write to file
258
+ # File.open('rtree.bsrt', 'w') { |io| rtree.bsrt_write(io) }
259
+ def bsrt_write(io)
260
+ super
261
+ end
262
+
263
+ # Serialise to JSON string
264
+ # @return [String] the UTF-8 encoded JSON
265
+ # @see .from_json
266
+ def to_json
267
+ serialise(Encoding::UTF_8) { |io| json_write(io) }
268
+ end
269
+
270
+ # Serialise to BSRT string
271
+ # @return [String] the binary encoded BSRT
272
+ # @see .from_bsrt
273
+ def to_bsrt
274
+ serialise(Encoding::BINARY) { |io| bsrt_write(io) }
275
+ end
276
+
277
+ # The RTree structure in hash form
278
+ # @return [Hash]
279
+ def to_h
280
+ JSON.parse(to_json, symbolize_names: true)
281
+ end
282
+
283
+ # Equality of RTrees. This is a rather strict equality,
284
+ # not only must the tree have the same rectangkes, they
285
+ # must be in the same order. Certainly {#clone} will produce
286
+ # an instance which is equal in this sense.
287
+ # @return [Boolean] true if the instances are identical
288
+ def eq?(other)
289
+ super
290
+ end
291
+
292
+ alias_method(:==, :eq?)
293
+
294
+ private
295
+
296
+ attr_reader :split, :node_page
297
+
298
+ def split_flag
299
+ self.class.split_flag(split)
300
+ end
301
+
302
+ def node_page_flag
303
+ self.class.node_page_flag(node_page)
304
+ end
305
+
306
+ def flags
307
+ node_page_flag | split_flag
308
+ end
309
+
310
+ def serialise(encoding)
311
+ rd, wr = IO.pipe(encoding)
312
+ if fork then
313
+ wr.close
314
+ result = rd.read
315
+ rd.close
316
+ Process.wait
317
+ else
318
+ rd.close
319
+ yield(wr)
320
+ wr.close
321
+ exit!
322
+ end
323
+ result
324
+ end
325
+
326
+ end
327
+
328
+ require 'rtree/rtree'
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: librtree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - J.J. Green
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubygems-tasks
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "<="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.2.4
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "<="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.2.4
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake-compiler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1'
97
+ description: |
98
+ A Ruby native extension for librtree implementing the R-tree
99
+ spatial-index of Guttman-Green.
100
+ email: j.j.green@gmx.co.uk
101
+ executables: []
102
+ extensions:
103
+ - ext/rtree/extconf.rb
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ext/rtree/extconf.rb
107
+ - ext/rtree/rtree.c
108
+ - lib/rtree.rb
109
+ homepage: https://gitlab.com/jjg/librtree-ruby
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ - ext
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements:
129
+ - The librtree library
130
+ rubygems_version: 3.1.2
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: R-tree spatial index
134
+ test_files: []