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.
- checksums.yaml +7 -0
- data/ext/rtree/extconf.rb +31 -0
- data/ext/rtree/rtree.c +362 -0
- data/lib/rtree.rb +328 -0
- 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: []
|