librtree 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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: []