lulu 0.0.2
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 +15 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +676 -0
- data/README.md +91 -0
- data/Rakefile +16 -0
- data/ext/lulu/Makefile +220 -0
- data/ext/lulu/extconf.rb +6 -0
- data/ext/lulu/lulu.c +271 -0
- data/ext/lulu/marker.c +111 -0
- data/ext/lulu/marker.h +70 -0
- data/ext/lulu/merger.c +191 -0
- data/ext/lulu/merger.h +17 -0
- data/ext/lulu/pq.c +244 -0
- data/ext/lulu/pq.h +70 -0
- data/ext/lulu/qt.c +299 -0
- data/ext/lulu/qt.h +57 -0
- data/ext/lulu/test.c +82 -0
- data/ext/lulu/test.h +20 -0
- data/ext/lulu/utility.c +49 -0
- data/ext/lulu/utility.h +74 -0
- data/lib/lulu/lulu.bundle +0 -0
- data/lib/lulu/version.rb +3 -0
- data/lib/lulu.rb +14 -0
- data/lulu.gemspec +32 -0
- data/spec/lib/lulu_spec.rb +14 -0
- data/spec/lib/marker_list_spec.rb +39 -0
- data/spec/spec_helper.rb +1 -0
- metadata +142 -0
data/ext/lulu/qt.c
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
/*
|
2
|
+
* qt.c
|
3
|
+
*
|
4
|
+
* Created on: Mar 17, 2014
|
5
|
+
* Author: generessler
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include <stdio.h>
|
9
|
+
#include <stdlib.h>
|
10
|
+
#include <assert.h>
|
11
|
+
#include "qt.h"
|
12
|
+
#include "utility.h"
|
13
|
+
#include "test.h"
|
14
|
+
|
15
|
+
// Declare the given quadrant of a given bounding box.
|
16
|
+
#define QUADRANT_DECL(Q, QX, QY, QW, QH, X, Y, W, H) \
|
17
|
+
double QW = W * 0.5; \
|
18
|
+
double QH = H * 0.5; \
|
19
|
+
double QX = (Q & 1) ? X + QW : X; \
|
20
|
+
double QY = (Q & 2) ? Y + QH : Y
|
21
|
+
|
22
|
+
// Initialize a node to an empty leaf.
|
23
|
+
static void init_leaf(NODE *node) {
|
24
|
+
node->children = NULL;
|
25
|
+
node->markers = NULL;
|
26
|
+
node->marker_count = node->markers_size = 0;
|
27
|
+
}
|
28
|
+
|
29
|
+
// Clear contents of a leaf, returning it to the init_leaf state.
|
30
|
+
static void clear_leaf(NODE *node) {
|
31
|
+
Free(node->markers);
|
32
|
+
init_leaf(node);
|
33
|
+
}
|
34
|
+
|
35
|
+
// Make a leaf into an internal node with four empty leaves.
|
36
|
+
static void subdivide(NODE *node) {
|
37
|
+
if (leaf_p(node)) {
|
38
|
+
NewArray(node->children, 4);
|
39
|
+
for (int i = 0; i < 4; i++)
|
40
|
+
init_leaf(node->children + i);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
static void clear_node(NODE *node);
|
45
|
+
|
46
|
+
// Recursively clear an internal node by removing all
|
47
|
+
// subtrees, returning this node to the init_leaf state.
|
48
|
+
static void clear_internal(NODE *node) {
|
49
|
+
for (int i = 0; i < 4; i++)
|
50
|
+
clear_node(node->children + i);
|
51
|
+
Free(node->children);
|
52
|
+
clear_leaf(node);
|
53
|
+
}
|
54
|
+
|
55
|
+
// Clear any node, returning it to the init_leaf state.
|
56
|
+
// Recursively removes all subtrees.
|
57
|
+
static void clear_node(NODE *node) {
|
58
|
+
if (internal_p(node))
|
59
|
+
clear_internal(node);
|
60
|
+
else
|
61
|
+
clear_leaf(node);
|
62
|
+
}
|
63
|
+
|
64
|
+
// Add a marker to the given node's marker list.
|
65
|
+
static void add_marker(NODE *node, MARKER *marker) {
|
66
|
+
if (node->marker_count == node->markers_size) {
|
67
|
+
node->markers_size = 2 + 2 * node->markers_size;
|
68
|
+
RenewArray(node->markers, node->markers_size);
|
69
|
+
}
|
70
|
+
node->markers[node->marker_count++] = marker;
|
71
|
+
}
|
72
|
+
|
73
|
+
// Find a marker in the given node's marker list.
|
74
|
+
static int find_marker(NODE *node, MARKER *marker) {
|
75
|
+
for (int i = 0; i < node->marker_count; i++)
|
76
|
+
if (node->markers[i] == marker)
|
77
|
+
return i;
|
78
|
+
return -1;
|
79
|
+
}
|
80
|
+
|
81
|
+
// Delete a marker from the given node's marker list.
|
82
|
+
static int delete_marker(NODE *node, MARKER *marker) {
|
83
|
+
int i = find_marker(node, marker);
|
84
|
+
if (i != -1 && --node->marker_count != 0)
|
85
|
+
node->markers[i] = node->markers[node->marker_count];
|
86
|
+
return i;
|
87
|
+
}
|
88
|
+
|
89
|
+
// Return non-zero iff the given bounding box lies inside the marker including its boundary.
|
90
|
+
static int bounds_inside_marker(double x, double y, double w, double h, MARKER *marker) {
|
91
|
+
double mx = mr_x(marker);
|
92
|
+
double my = mr_y(marker);
|
93
|
+
double mr = mr_r(marker);
|
94
|
+
return mx - mr <= x && x + w <= mx + mr && my - mr <= y && y + h <= my + mr ;
|
95
|
+
}
|
96
|
+
|
97
|
+
// Return an integer code with bits showing which quadrants of the given
|
98
|
+
// bounding box are overlapped by the given marker.
|
99
|
+
static int touch_code(double x, double y, double w, double h, MARKER *marker) {
|
100
|
+
double xm = x + 0.5 * w;
|
101
|
+
double ym = y + 0.5 * h;
|
102
|
+
int code = bit(SW) | bit(SE) | bit(NW) | bit(NE);
|
103
|
+
if (mr_e(marker) < xm) code &= ~(bit(NE) | bit(SE));
|
104
|
+
if (mr_w(marker) > xm) code &= ~(bit(NW) | bit(SW));
|
105
|
+
if (mr_n(marker) < ym) code &= ~(bit(NW) | bit(NE));
|
106
|
+
if (mr_s(marker) > ym) code &= ~(bit(SW) | bit(SE));
|
107
|
+
return code;
|
108
|
+
}
|
109
|
+
|
110
|
+
// Insert the given marker into the quadtree with given root and corresponding bounding box,
|
111
|
+
// subdividing no more than the given number of levels.
|
112
|
+
static void insert(NODE *node, int levels, double x, double y, double w, double h, MARKER *marker) {
|
113
|
+
if (bounds_inside_marker(x, y, w, h, marker) || levels == 0)
|
114
|
+
add_marker(node, marker);
|
115
|
+
else {
|
116
|
+
if (leaf_p(node))
|
117
|
+
subdivide(node);
|
118
|
+
int code = touch_code(x, y, w, h, marker);
|
119
|
+
for (int q = 0; q < 4; q++)
|
120
|
+
if (code & bit(q)) {
|
121
|
+
QUADRANT_DECL(q, qx, qy, qw, qh, x, y, w, h);
|
122
|
+
insert(node->children + q, levels - 1, qx, qy, qw, qh, marker);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
// A helper function that returns true iff the given array of 4 child quadtrees are all empty leaves.
|
128
|
+
static int empty_leaves_p(NODE *children) {
|
129
|
+
for (int i = 0; i < 4; i++)
|
130
|
+
if (internal_p(children + i) || children[i].marker_count > 0)
|
131
|
+
return 0;
|
132
|
+
return 1;
|
133
|
+
}
|
134
|
+
|
135
|
+
// Delete the given marker from the quadtree with given root and corresponding bounding box,
|
136
|
+
// trimming any remaining empty leaves.
|
137
|
+
static void delete(NODE *node, int levels, double x, double y, double w, double h, MARKER *marker) {
|
138
|
+
if (bounds_inside_marker(x, y, w, h, marker) || levels == 0)
|
139
|
+
delete_marker(node, marker);
|
140
|
+
else if (internal_p(node)){
|
141
|
+
int code = touch_code(x, y, w, h, marker);
|
142
|
+
for (int q = 0; q < 4; q++)
|
143
|
+
if (code & bit(q)) {
|
144
|
+
QUADRANT_DECL(q, qx, qy, qw, qh, x, y, w, h);
|
145
|
+
delete(node->children + q, levels - 1, qx, qy, qw, qh, marker);
|
146
|
+
}
|
147
|
+
if (empty_leaves_p(node->children))
|
148
|
+
Free(node->children);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
// Local struct to hold information about the nearest marker seen so far in a search.
|
153
|
+
struct nearest_info {
|
154
|
+
MARKER_INFO *info;
|
155
|
+
MARKER *target, *nearest;
|
156
|
+
double distance;
|
157
|
+
};
|
158
|
+
|
159
|
+
// Use the marker list of the given node to update nearest information with
|
160
|
+
// respect to the given marker.
|
161
|
+
static void update_nearest(NODE *node, struct nearest_info *nearest_info) {
|
162
|
+
for (int i = 0; i < node->marker_count; i++) {
|
163
|
+
// This < assumes the markers are in an array. It sustains the invariant.
|
164
|
+
if (node->markers[i] < nearest_info->target) {
|
165
|
+
double d = mr_distance(nearest_info->info, nearest_info->target, node->markers[i]);
|
166
|
+
if (d < nearest_info->distance) {
|
167
|
+
nearest_info->distance = d;
|
168
|
+
nearest_info->nearest = node->markers[i];
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
// Search out the nearest marker that overlaps the given one. This just visits every
|
175
|
+
// quad that overlaps the given marker and remembers the closest marker it sees. The
|
176
|
+
// circle distance function renders quite impossible the ruling out of quads as in
|
177
|
+
// nearest point neighbor search.
|
178
|
+
static void search_for_nearest(NODE *node, double x, double y, double w, double h, struct nearest_info *nearest_info) {
|
179
|
+
update_nearest(node, nearest_info);
|
180
|
+
if (internal_p(node)) {
|
181
|
+
// Search the children that include some part of the marker.
|
182
|
+
int code = touch_code(x, y, w, h, nearest_info->target);
|
183
|
+
for (int q = 0; q < 4; q++)
|
184
|
+
if (code & bit(q)) {
|
185
|
+
QUADRANT_DECL(q, qx, qy, qw, qh, x, y, w, h);
|
186
|
+
search_for_nearest(node->children + q, qx, qy, qw, qh, nearest_info);
|
187
|
+
}
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
static MARKER *nearest(MARKER_INFO *info, NODE *node, double x, double y, double w, double h, MARKER *marker) {
|
192
|
+
struct nearest_info nearest_info[1] = {{ info, marker, NULL, 0 }};
|
193
|
+
search_for_nearest(node, x, y, w, h, nearest_info);
|
194
|
+
return nearest_info->nearest;
|
195
|
+
}
|
196
|
+
|
197
|
+
void qt_init(QUADTREE *qt) {
|
198
|
+
init_leaf(qt->root);
|
199
|
+
qt->x = qt->y = qt->w = qt->h = 0;
|
200
|
+
}
|
201
|
+
|
202
|
+
void qt_setup(QUADTREE *qt, int max_depth, double x, double y, double w, double h, MARKER_INFO *info) {
|
203
|
+
qt->x = x;
|
204
|
+
qt->y = y;
|
205
|
+
qt->w = w;
|
206
|
+
qt->h = h;
|
207
|
+
qt->max_depth = max_depth;
|
208
|
+
qt->info = info;
|
209
|
+
}
|
210
|
+
|
211
|
+
void qt_clear(QUADTREE *qt) {
|
212
|
+
clear_node(qt->root);
|
213
|
+
qt_init(qt);
|
214
|
+
}
|
215
|
+
|
216
|
+
void qt_insert(QUADTREE *qt, MARKER *marker) {
|
217
|
+
double x = mr_x(marker);
|
218
|
+
double y = mr_y(marker);
|
219
|
+
double r = mr_r(marker);
|
220
|
+
if (x + r >= qt->x && x - r <= qt->x + qt->w && y + r >= qt->y && y - r <= qt->y + qt->h)
|
221
|
+
insert(qt->root, qt->max_depth, qt->x, qt->y, qt->w, qt->h, marker);
|
222
|
+
}
|
223
|
+
|
224
|
+
void qt_delete(QUADTREE *qt, MARKER *marker) {
|
225
|
+
delete(qt->root, qt->max_depth, qt->x, qt->y, qt->w, qt->h, marker);
|
226
|
+
}
|
227
|
+
|
228
|
+
MARKER *qt_nearest(QUADTREE *qt, MARKER *marker) {
|
229
|
+
return nearest(qt->info, qt->root, qt->x, qt->y, qt->w, qt->h, marker);
|
230
|
+
}
|
231
|
+
|
232
|
+
int qt_nearest_wrt(MARKER *markers, QUADTREE *qt, int a) {
|
233
|
+
MARKER *nearest = qt_nearest(qt, markers + a);
|
234
|
+
return nearest ? (int)(nearest - markers) : -1;
|
235
|
+
}
|
236
|
+
|
237
|
+
#ifndef EXCLUDE_UNIT_TEST
|
238
|
+
|
239
|
+
static void draw(FILE *f, NODE *node, double x, double y, double w, double h) {
|
240
|
+
emit_rectangle(f, x, y, w, h);
|
241
|
+
emit_marker_ptr_array(f, node->markers, node->marker_count);
|
242
|
+
if (internal_p(node)) {
|
243
|
+
for (int q = 0; q < 4; q++) {
|
244
|
+
QUADRANT_DECL(q, qx, qy, qw, qh, x, y, w, h);
|
245
|
+
draw(f, node->children + q, qx, qy, qw, qh);
|
246
|
+
}
|
247
|
+
}
|
248
|
+
}
|
249
|
+
|
250
|
+
static void draw_nearest(FILE *f, MARKER *markers, MARKER **nearest_markers, int n_markers) {
|
251
|
+
for (int i = 0; i < n_markers; i++) {
|
252
|
+
if (nearest_markers[i])
|
253
|
+
emit_segment(f, markers + i, nearest_markers[i]);
|
254
|
+
}
|
255
|
+
}
|
256
|
+
|
257
|
+
int qt_draw(QUADTREE *qt, MARKER *markers, MARKER **nearest_markers, int n_markers, const char *name) {
|
258
|
+
char buf[1024];
|
259
|
+
sprintf(buf, "test/%s.js", name);
|
260
|
+
FILE *f = fopen(buf, "w");
|
261
|
+
if (!f)
|
262
|
+
return -1;
|
263
|
+
fprintf(f, "var %s = [\n", name);
|
264
|
+
draw(f, qt->root, qt->x, qt->y, qt->w, qt->h);
|
265
|
+
draw_nearest(f, markers, nearest_markers, n_markers);
|
266
|
+
fprintf(f, "];\n");
|
267
|
+
fclose(f);
|
268
|
+
return EXIT_SUCCESS;
|
269
|
+
}
|
270
|
+
|
271
|
+
int qt_test(int size) {
|
272
|
+
QUADTREE_DECL(qt);
|
273
|
+
MARKER_INFO_DECL(info);
|
274
|
+
MARKER *markers, **nearest_markers;
|
275
|
+
NewArray(markers, size);
|
276
|
+
NewArray(nearest_markers, size);
|
277
|
+
set_random_markers(info, markers, size);
|
278
|
+
qt_setup(qt, 5, 0, 0, 1024, 724, info);
|
279
|
+
fprintf(stderr, "inserting %d:\n", size);
|
280
|
+
for (int i = 0; i < size; i++) {
|
281
|
+
qt_insert(qt, markers + i);
|
282
|
+
}
|
283
|
+
fprintf(stderr, "inserted %d\n", size);
|
284
|
+
for (int i = 0; i < size; i++) {
|
285
|
+
nearest_markers[i] = qt_nearest(qt, markers + i);
|
286
|
+
}
|
287
|
+
fprintf(stderr, "looked up %d\n", size);
|
288
|
+
qt_draw(qt, markers, nearest_markers, size, "qt");
|
289
|
+
fprintf(stderr, "drew %d\n", size);
|
290
|
+
for (int i = 0; i < size; i++) {
|
291
|
+
qt_delete(qt, markers + i);
|
292
|
+
}
|
293
|
+
fprintf(stderr, "after delete all, root is %s\n",
|
294
|
+
leaf_p(qt->root) ? "leaf (ok)" : "internal (not ok)");
|
295
|
+
|
296
|
+
return EXIT_SUCCESS;
|
297
|
+
}
|
298
|
+
|
299
|
+
#endif
|
data/ext/lulu/qt.h
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
/*
|
2
|
+
* qt.h
|
3
|
+
*
|
4
|
+
* Created on: Mar 17, 2014
|
5
|
+
* Author: generessler
|
6
|
+
*/
|
7
|
+
|
8
|
+
#ifndef QT_H_
|
9
|
+
#define QT_H_
|
10
|
+
|
11
|
+
#include "marker.h"
|
12
|
+
|
13
|
+
typedef struct node_s {
|
14
|
+
struct node_s *children;
|
15
|
+
MARKER **markers;
|
16
|
+
int marker_count, markers_size;
|
17
|
+
} NODE;
|
18
|
+
|
19
|
+
#define leaf_p(Node) ((Node)->children == NULL)
|
20
|
+
#define internal_p(Node) ((Node)->children != NULL)
|
21
|
+
|
22
|
+
// These are chosen so the 1's bit encodes the
|
23
|
+
// quadrant x location and the 2's bit encodes y
|
24
|
+
//
|
25
|
+
// ---------------
|
26
|
+
// | | |
|
27
|
+
// | 1 0 | 1 1 |
|
28
|
+
// | | |
|
29
|
+
// ---------------
|
30
|
+
// | | |
|
31
|
+
// | 0 0 | 0 1 |
|
32
|
+
// | | |
|
33
|
+
// ---------------
|
34
|
+
#define SW 0
|
35
|
+
#define SE 1
|
36
|
+
#define NW 2
|
37
|
+
#define NE 3
|
38
|
+
|
39
|
+
typedef struct quadtree_s {
|
40
|
+
double x, y, w, h;
|
41
|
+
int max_depth;
|
42
|
+
MARKER_INFO *info;
|
43
|
+
NODE root[1];
|
44
|
+
} QUADTREE;
|
45
|
+
|
46
|
+
#define QUADTREE_DECL(Name) QUADTREE Name[1]; qt_init(Name)
|
47
|
+
|
48
|
+
void qt_init(QUADTREE *qt);
|
49
|
+
void qt_setup(QUADTREE *qt, int max_depth, double x, double y, double w, double h, MARKER_INFO *info);
|
50
|
+
void qt_insert(QUADTREE *qt, MARKER *marker);
|
51
|
+
void qt_delete(QUADTREE *qt, MARKER *marker);
|
52
|
+
MARKER *qt_nearest(QUADTREE *qt, MARKER *marker);
|
53
|
+
int qt_nearest_wrt(MARKER *markers, QUADTREE *qt, int a);
|
54
|
+
void qt_clear(QUADTREE *qt);
|
55
|
+
int qt_test(int size);
|
56
|
+
|
57
|
+
#endif /* QT_H_ */
|
data/ext/lulu/test.c
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
/*
|
2
|
+
* test.c
|
3
|
+
*
|
4
|
+
* Created on: Mar 21, 2014
|
5
|
+
* Author: generessler
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include <stdio.h>
|
9
|
+
#include <stdlib.h>
|
10
|
+
#include <stdarg.h>
|
11
|
+
#include <time.h>
|
12
|
+
#include "test.h"
|
13
|
+
#include "utility.h"
|
14
|
+
|
15
|
+
void emit_rectangle(FILE *f, double x, double y, double w, double h) {
|
16
|
+
fprintf(f, " { kind: 'r', color: 'lightGray', p0: { x: %.2f, y: %.2f }, p1: { x: %.2f, y: %.2f } },\n", x, y, x + w, y + h);
|
17
|
+
}
|
18
|
+
|
19
|
+
void emit_segment(FILE *f, MARKER *a, MARKER *b) {
|
20
|
+
fprintf(f, " { kind: 's', color: 'red', p0: { x: %.2f, y: %.2f }, p1: { x: %.2f, y: %.2f } },\n",
|
21
|
+
mr_x(a), mr_y(a), mr_x(b), mr_y(b));
|
22
|
+
}
|
23
|
+
|
24
|
+
int emit_marker_ptr_array(FILE *f, MARKER **markers, int n_markers) {
|
25
|
+
int n_emitted = 0;
|
26
|
+
for (int i = 0; i < n_markers; ++i) {
|
27
|
+
MARKER *m = markers[i];
|
28
|
+
if (!m->deleted_p) {
|
29
|
+
fprintf(f, " { kind: 'm', x: %.2f, y: %.2f, r: %.2f }, // %d\n", mr_x(m), mr_y(m), mr_r(m), i);
|
30
|
+
n_emitted++;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
return n_emitted;
|
34
|
+
}
|
35
|
+
|
36
|
+
int emit_marker_array(FILE *f, MARKER *markers, int n_markers) {
|
37
|
+
int n_emitted = 0;
|
38
|
+
for (int i = 0; i < n_markers; ++i) {
|
39
|
+
MARKER *m = markers + i;
|
40
|
+
if (!m->deleted_p) {
|
41
|
+
fprintf(f, " { x: %.2f, y: %.2f, r: %.2f }, // %d\n", mr_x(m), mr_y(m), mr_r(m), i);
|
42
|
+
n_emitted++;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
return n_emitted;
|
46
|
+
}
|
47
|
+
|
48
|
+
int emit_markers(const char *name, MARKER *markers, int n_markers) {
|
49
|
+
char buf[1024];
|
50
|
+
sprintf(buf, "test/%s.js", name);
|
51
|
+
FILE *f = fopen(buf, "w");
|
52
|
+
if (!f)
|
53
|
+
return -1;
|
54
|
+
fprintf(f, "var %s = [\n", name);
|
55
|
+
int n_emitted = emit_marker_array(f, markers, n_markers);
|
56
|
+
fprintf(f, "];");
|
57
|
+
fclose(f);
|
58
|
+
return n_emitted;
|
59
|
+
}
|
60
|
+
|
61
|
+
void set_random_markers(MARKER_INFO *info, MARKER *markers, int n_markers) {
|
62
|
+
unsigned t = (unsigned)time(0);
|
63
|
+
fprintf(stderr, "seed=%u\n", t);
|
64
|
+
srand(t);
|
65
|
+
int size = 8;
|
66
|
+
double x_size = 1024;
|
67
|
+
double y_size = 760;
|
68
|
+
for (int i = 0; i < n_markers; i++)
|
69
|
+
mr_set(info, markers + i, x_size * rand_double(), y_size * rand_double(), 1 + random() % (size - 1));
|
70
|
+
}
|
71
|
+
|
72
|
+
int trace_p = 1;
|
73
|
+
|
74
|
+
void trace(const char *fmt, ...)
|
75
|
+
{
|
76
|
+
if (trace_p) {
|
77
|
+
va_list ap;
|
78
|
+
va_start(ap, fmt);
|
79
|
+
vfprintf(stderr, fmt, ap);
|
80
|
+
va_end(ap);
|
81
|
+
}
|
82
|
+
}
|
data/ext/lulu/test.h
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
/*
|
2
|
+
* test.h
|
3
|
+
*
|
4
|
+
* Created on: Mar 21, 2014
|
5
|
+
* Author: generessler
|
6
|
+
*/
|
7
|
+
|
8
|
+
#ifndef TEST_H_
|
9
|
+
#define TEST_H_
|
10
|
+
|
11
|
+
#include "marker.h"
|
12
|
+
|
13
|
+
int emit_markers(const char *name, MARKER *markers, int n_markers);
|
14
|
+
void emit_rectangle(FILE *f, double x, double y, double w, double h);
|
15
|
+
void emit_segment(FILE *f, MARKER *a, MARKER *b);
|
16
|
+
int emit_marker_array(FILE *f, MARKER *markers, int n_markers);
|
17
|
+
int emit_marker_ptr_array(FILE *f, MARKER **markers, int n_markers);
|
18
|
+
void set_random_markers(MARKER_INFO *info, MARKER *markers, int n_markers);
|
19
|
+
|
20
|
+
#endif /* TEST_H_ */
|
data/ext/lulu/utility.c
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
/*
|
2
|
+
* utility.c
|
3
|
+
*
|
4
|
+
* Created on: Feb 23, 2014
|
5
|
+
* Author: generessler
|
6
|
+
*/
|
7
|
+
|
8
|
+
#include <stdio.h>
|
9
|
+
#include <stdlib.h>
|
10
|
+
#include "utility.h"
|
11
|
+
|
12
|
+
void *safe_malloc(size_t size, const char *file, int line) {
|
13
|
+
void *p = malloc(size);
|
14
|
+
if (!p) {
|
15
|
+
fprintf(stderr, "%s:%d: out of memory\n", file, line);
|
16
|
+
exit(1);
|
17
|
+
}
|
18
|
+
return p;
|
19
|
+
}
|
20
|
+
|
21
|
+
void *safe_realloc(void *p, size_t size, const char *file, int line) {
|
22
|
+
p = realloc(p, size);
|
23
|
+
if (!p) {
|
24
|
+
fprintf(stderr, "%s:%d: out of memory\n", file, line);
|
25
|
+
exit(1);
|
26
|
+
}
|
27
|
+
return p;
|
28
|
+
}
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Return the 0-based position of highest bit or -1 of zero.
|
32
|
+
*/
|
33
|
+
int high_bit_position(unsigned n) {
|
34
|
+
int p = -1;
|
35
|
+
while (n) {
|
36
|
+
p++;
|
37
|
+
n >>= 1;
|
38
|
+
}
|
39
|
+
return p;
|
40
|
+
}
|
41
|
+
|
42
|
+
double rand_double(void)
|
43
|
+
{
|
44
|
+
unsigned i = rand();
|
45
|
+
i = (i << 8) ^ (unsigned)rand();
|
46
|
+
i = (i << 8) ^ (unsigned)rand();
|
47
|
+
i = (i << 8) ^ (unsigned)rand();
|
48
|
+
return i / (256.0 * 256.0 * 256.0 * 256.0);
|
49
|
+
}
|
data/ext/lulu/utility.h
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
/*
|
2
|
+
* utility.h
|
3
|
+
*
|
4
|
+
* Created on: Feb 23, 2014
|
5
|
+
* Author: generessler
|
6
|
+
*/
|
7
|
+
|
8
|
+
#ifndef UTILITY_H_
|
9
|
+
#define UTILITY_H_
|
10
|
+
|
11
|
+
#define STATIC_ARRAY_SIZE(A) ((int)(sizeof A / sizeof A[0]))
|
12
|
+
|
13
|
+
#ifdef NOT_RUBY_EXTENSION
|
14
|
+
|
15
|
+
// Our allocators.
|
16
|
+
#define New(Ptr) do { \
|
17
|
+
(Ptr) = safe_malloc(sizeof *(Ptr), __FILE__, __LINE__); \
|
18
|
+
} while (0)
|
19
|
+
|
20
|
+
#define NewArray(Ptr, Size) do { \
|
21
|
+
(Ptr) = safe_malloc((Size) * sizeof *(Ptr), __FILE__, __LINE__); \
|
22
|
+
} while (0)
|
23
|
+
|
24
|
+
#define RenewArray(Ptr, Size) do { \
|
25
|
+
(Ptr) = safe_realloc((Ptr), (Size) * sizeof *(Ptr), __FILE__, __LINE__); \
|
26
|
+
} while (0)
|
27
|
+
|
28
|
+
#define Free(Ptr) do { \
|
29
|
+
free(Ptr); \
|
30
|
+
Ptr = NULL; \
|
31
|
+
} while (0)
|
32
|
+
|
33
|
+
#else
|
34
|
+
|
35
|
+
#include "ruby.h"
|
36
|
+
|
37
|
+
// Ruby allocators. We can't use the macros without rewriting to include type parameters.
|
38
|
+
#define New(Ptr) do { \
|
39
|
+
(Ptr) = (void*)xmalloc(sizeof *(Ptr)); \
|
40
|
+
} while (0)
|
41
|
+
|
42
|
+
#define NewArray(Ptr, Size) do { \
|
43
|
+
(Ptr) = (void*)xmalloc2((Size), sizeof *(Ptr)); \
|
44
|
+
} while (0)
|
45
|
+
|
46
|
+
#define RenewArray(Ptr, Size) do { \
|
47
|
+
(Ptr) = (void*)xrealloc2((char*)(Ptr), (Size), sizeof *(Ptr)); \
|
48
|
+
} while (0)
|
49
|
+
|
50
|
+
#define Free(Ptr) do { \
|
51
|
+
Ptr = NULL; \
|
52
|
+
} while (0)
|
53
|
+
|
54
|
+
#endif
|
55
|
+
|
56
|
+
#define NewDecl(Type, Ptr) Type *Ptr; New(Ptr)
|
57
|
+
#define NewArrayDecl(Type, Ptr, Size) Type *Ptr; NewArray(Ptr, Size)
|
58
|
+
#define CopyArray(Dst, Src, N) memcpy((Dst), (Src), (N) * sizeof *(Src))
|
59
|
+
|
60
|
+
#define bit(N) (1u << (N))
|
61
|
+
|
62
|
+
#ifndef NTRACE
|
63
|
+
#define TRACE(Args) trace Args
|
64
|
+
void trace(const char *fmt, ...);
|
65
|
+
#else
|
66
|
+
#define TRACE(Args)
|
67
|
+
#endif
|
68
|
+
|
69
|
+
void *safe_malloc(size_t size, const char *file, int line);
|
70
|
+
void *safe_realloc(void *p, size_t size, const char *file, int line);
|
71
|
+
int high_bit_position(unsigned n);
|
72
|
+
double rand_double(void);
|
73
|
+
|
74
|
+
#endif /* UTILITY_H_ */
|
Binary file
|
data/lib/lulu/version.rb
ADDED
data/lib/lulu.rb
ADDED
data/lulu.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lulu/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "lulu"
|
8
|
+
spec.version = Lulu::VERSION
|
9
|
+
spec.authors = ["Gene Ressler"]
|
10
|
+
spec.email = ["gene.ressler@gmail.com"]
|
11
|
+
spec.summary = %q{Merge map markers by agglomerative clustering.}
|
12
|
+
spec.description =
|
13
|
+
%q{Lulu merges the closest overlapping pair of map markers into a one whose
|
14
|
+
area is the sum of the original two, located at their centroid. It repeats
|
15
|
+
this until no overlaps remain. Some interesting data structures make this
|
16
|
+
quite fast for all but pathologically bad data. Markers can be circles or
|
17
|
+
squares. The user may provide a scale factor allowing the marker radius to
|
18
|
+
have a different scale than distance between markers.}
|
19
|
+
spec.homepage = "https://github.com/gene-ressler/lulu/wiki"
|
20
|
+
spec.licenses = "GPL-3.0"
|
21
|
+
|
22
|
+
spec.files = `git ls-files`.split($/)
|
23
|
+
spec.extensions = ['ext/lulu/extconf.rb']
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.1"
|
30
|
+
spec.add_development_dependency "rake-compiler"
|
31
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Test the Lulu API.
|
4
|
+
describe Lulu::MarkerList do
|
5
|
+
|
6
|
+
TEST_SIZE = 10000
|
7
|
+
|
8
|
+
let(:list) do
|
9
|
+
list = Lulu::MarkerList.new
|
10
|
+
srand(42)
|
11
|
+
TEST_SIZE.times{ |n| list.add(Random.rand(1000), Random.rand(1000), Random.rand(100)) }
|
12
|
+
list
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should have length matching number of markers added' do
|
16
|
+
list.length.should == TEST_SIZE
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should merge to correct number of markers' do
|
20
|
+
list.merge.should == 17362
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should compress to correct number of markers after merge' do
|
24
|
+
list.merge
|
25
|
+
list.compress.should == 2638
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should have idempotent merge' do
|
29
|
+
list.merge
|
30
|
+
list.compress == list.merge
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should have correct number of non-merged nodes in parts' do
|
34
|
+
n = 0
|
35
|
+
list.merge.times{|i| n += 1 if [:root, :single].include? list.parts(i)[0] }
|
36
|
+
n.should == list.compress
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'lulu'
|