xref: /sqlite-3.40.0/src/test_rtree.c (revision b640595f)
1 /*
2 ** 2010 August 28
3 **
4 ** The author disclaims copyright to this source code.  In place of
5 ** a legal notice, here is a blessing:
6 **
7 **    May you do good and not evil.
8 **    May you find forgiveness for yourself and forgive others.
9 **    May you share freely, never taking more than you give.
10 **
11 *************************************************************************
12 ** Code for testing all sorts of SQLite interfaces. This code
13 ** is not included in the SQLite library.
14 */
15 
16 #include <sqlite3.h>
17 #include <tcl.h>
18 
19 /* Solely for the UNUSED_PARAMETER() macro. */
20 #include "sqliteInt.h"
21 
22 #ifdef SQLITE_ENABLE_RTREE
23 /*
24 ** Type used to cache parameter information for the "circle" r-tree geometry
25 ** callback.
26 */
27 typedef struct Circle Circle;
28 struct Circle {
29   struct Box {
30     double xmin;
31     double xmax;
32     double ymin;
33     double ymax;
34   } aBox[2];
35   double centerx;
36   double centery;
37   double radius;
38   double mxArea;
39 };
40 
41 /*
42 ** Destructor function for Circle objects allocated by circle_geom().
43 */
44 static void circle_del(void *p){
45   sqlite3_free(p);
46 }
47 
48 /*
49 ** Implementation of "circle" r-tree geometry callback.
50 */
51 static int circle_geom(
52   sqlite3_rtree_geometry *p,
53   int nCoord,
54   sqlite3_rtree_dbl *aCoord,
55   int *pRes
56 ){
57   int i;                          /* Iterator variable */
58   Circle *pCircle;                /* Structure defining circular region */
59   double xmin, xmax;              /* X dimensions of box being tested */
60   double ymin, ymax;              /* X dimensions of box being tested */
61 
62   xmin = aCoord[0];
63   xmax = aCoord[1];
64   ymin = aCoord[2];
65   ymax = aCoord[3];
66   pCircle = (Circle *)p->pUser;
67   if( pCircle==0 ){
68     /* If pUser is still 0, then the parameter values have not been tested
69     ** for correctness or stored into a Circle structure yet. Do this now. */
70 
71     /* This geometry callback is for use with a 2-dimensional r-tree table.
72     ** Return an error if the table does not have exactly 2 dimensions. */
73     if( nCoord!=4 ) return SQLITE_ERROR;
74 
75     /* Test that the correct number of parameters (3) have been supplied,
76     ** and that the parameters are in range (that the radius of the circle
77     ** radius is greater than zero). */
78     if( p->nParam!=3 || p->aParam[2]<0.0 ) return SQLITE_ERROR;
79 
80     /* Allocate a structure to cache parameter data in. Return SQLITE_NOMEM
81     ** if the allocation fails. */
82     pCircle = (Circle *)(p->pUser = sqlite3_malloc(sizeof(Circle)));
83     if( !pCircle ) return SQLITE_NOMEM;
84     p->xDelUser = circle_del;
85 
86     /* Record the center and radius of the circular region. One way that
87     ** tested bounding boxes that intersect the circular region are detected
88     ** is by testing if each corner of the bounding box lies within radius
89     ** units of the center of the circle. */
90     pCircle->centerx = p->aParam[0];
91     pCircle->centery = p->aParam[1];
92     pCircle->radius = p->aParam[2];
93 
94     /* Define two bounding box regions. The first, aBox[0], extends to
95     ** infinity in the X dimension. It covers the same range of the Y dimension
96     ** as the circular region. The second, aBox[1], extends to infinity in
97     ** the Y dimension and is constrained to the range of the circle in the
98     ** X dimension.
99     **
100     ** Then imagine each box is split in half along its short axis by a line
101     ** that intersects the center of the circular region. A bounding box
102     ** being tested can be said to intersect the circular region if it contains
103     ** points from each half of either of the two infinite bounding boxes.
104     */
105     pCircle->aBox[0].xmin = pCircle->centerx;
106     pCircle->aBox[0].xmax = pCircle->centerx;
107     pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius;
108     pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius;
109     pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius;
110     pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius;
111     pCircle->aBox[1].ymin = pCircle->centery;
112     pCircle->aBox[1].ymax = pCircle->centery;
113     pCircle->mxArea = (xmax - xmin)*(ymax - ymin) + 1.0;
114   }
115 
116   /* Check if any of the 4 corners of the bounding-box being tested lie
117   ** inside the circular region. If they do, then the bounding-box does
118   ** intersect the region of interest. Set the output variable to true and
119   ** return SQLITE_OK in this case. */
120   for(i=0; i<4; i++){
121     double x = (i&0x01) ? xmax : xmin;
122     double y = (i&0x02) ? ymax : ymin;
123     double d2;
124 
125     d2  = (x-pCircle->centerx)*(x-pCircle->centerx);
126     d2 += (y-pCircle->centery)*(y-pCircle->centery);
127     if( d2<(pCircle->radius*pCircle->radius) ){
128       *pRes = 1;
129       return SQLITE_OK;
130     }
131   }
132 
133   /* Check if the bounding box covers any other part of the circular region.
134   ** See comments above for a description of how this test works. If it does
135   ** cover part of the circular region, set the output variable to true
136   ** and return SQLITE_OK. */
137   for(i=0; i<2; i++){
138     if( xmin<=pCircle->aBox[i].xmin
139      && xmax>=pCircle->aBox[i].xmax
140      && ymin<=pCircle->aBox[i].ymin
141      && ymax>=pCircle->aBox[i].ymax
142     ){
143       *pRes = 1;
144       return SQLITE_OK;
145     }
146   }
147 
148   /* The specified bounding box does not intersect the circular region. Set
149   ** the output variable to zero and return SQLITE_OK. */
150   *pRes = 0;
151   return SQLITE_OK;
152 }
153 
154 /*
155 ** Implementation of "circle" r-tree geometry callback using the
156 ** 2nd-generation interface that allows scoring.
157 */
158 static int circle_query_func(sqlite3_rtree_query_info *p){
159   int i;                          /* Iterator variable */
160   Circle *pCircle;                /* Structure defining circular region */
161   double xmin, xmax;              /* X dimensions of box being tested */
162   double ymin, ymax;              /* X dimensions of box being tested */
163   int nWithin = 0;                /* Number of corners inside the circle */
164 
165   xmin = p->aCoord[0];
166   xmax = p->aCoord[1];
167   ymin = p->aCoord[2];
168   ymax = p->aCoord[3];
169   pCircle = (Circle *)p->pUser;
170   if( pCircle==0 ){
171     /* If pUser is still 0, then the parameter values have not been tested
172     ** for correctness or stored into a Circle structure yet. Do this now. */
173 
174     /* This geometry callback is for use with a 2-dimensional r-tree table.
175     ** Return an error if the table does not have exactly 2 dimensions. */
176     if( p->nCoord!=4 ) return SQLITE_ERROR;
177 
178     /* Test that the correct number of parameters (3) have been supplied,
179     ** and that the parameters are in range (that the radius of the circle
180     ** radius is greater than zero). */
181     if( p->nParam!=3 || p->aParam[2]<0.0 ) return SQLITE_ERROR;
182 
183     /* Allocate a structure to cache parameter data in. Return SQLITE_NOMEM
184     ** if the allocation fails. */
185     pCircle = (Circle *)(p->pUser = sqlite3_malloc(sizeof(Circle)));
186     if( !pCircle ) return SQLITE_NOMEM;
187     p->xDelUser = circle_del;
188 
189     /* Record the center and radius of the circular region. One way that
190     ** tested bounding boxes that intersect the circular region are detected
191     ** is by testing if each corner of the bounding box lies within radius
192     ** units of the center of the circle. */
193     pCircle->centerx = p->aParam[0];
194     pCircle->centery = p->aParam[1];
195     pCircle->radius = p->aParam[2];
196 
197     /* Define two bounding box regions. The first, aBox[0], extends to
198     ** infinity in the X dimension. It covers the same range of the Y dimension
199     ** as the circular region. The second, aBox[1], extends to infinity in
200     ** the Y dimension and is constrained to the range of the circle in the
201     ** X dimension.
202     **
203     ** Then imagine each box is split in half along its short axis by a line
204     ** that intersects the center of the circular region. A bounding box
205     ** being tested can be said to intersect the circular region if it contains
206     ** points from each half of either of the two infinite bounding boxes.
207     */
208     pCircle->aBox[0].xmin = pCircle->centerx;
209     pCircle->aBox[0].xmax = pCircle->centerx;
210     pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius;
211     pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius;
212     pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius;
213     pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius;
214     pCircle->aBox[1].ymin = pCircle->centery;
215     pCircle->aBox[1].ymax = pCircle->centery;
216     pCircle->mxArea = 200.0*200.0;
217   }
218 
219   /* Check if any of the 4 corners of the bounding-box being tested lie
220   ** inside the circular region. If they do, then the bounding-box does
221   ** intersect the region of interest. Set the output variable to true and
222   ** return SQLITE_OK in this case. */
223   for(i=0; i<4; i++){
224     double x = (i&0x01) ? xmax : xmin;
225     double y = (i&0x02) ? ymax : ymin;
226     double d2;
227 
228     d2  = (x-pCircle->centerx)*(x-pCircle->centerx);
229     d2 += (y-pCircle->centery)*(y-pCircle->centery);
230     if( d2<(pCircle->radius*pCircle->radius) ) nWithin++;
231   }
232 
233   /* Check if the bounding box covers any other part of the circular region.
234   ** See comments above for a description of how this test works. If it does
235   ** cover part of the circular region, set the output variable to true
236   ** and return SQLITE_OK. */
237   if( nWithin==0 ){
238     for(i=0; i<2; i++){
239       if( xmin<=pCircle->aBox[i].xmin
240        && xmax>=pCircle->aBox[i].xmax
241        && ymin<=pCircle->aBox[i].ymin
242        && ymax>=pCircle->aBox[i].ymax
243       ){
244         nWithin = 1;
245         break;
246       }
247     }
248   }
249 
250   if( p->iLevel==2 ){
251     p->rScore = 1.0 - (xmax-xmin)*(ymax-ymin)/pCircle->mxArea;
252     if( p->rScore<0.01 ) p->rScore = 0.01;
253   }else{
254     p->rScore = 0.0;
255   }
256   if( nWithin==0 ){
257     p->eWithin = NOT_WITHIN;
258   }else if( nWithin>=4 ){
259     p->eWithin = FULLY_WITHIN;
260   }else{
261     p->eWithin = PARTLY_WITHIN;
262   }
263   return SQLITE_OK;
264 }
265 
266 /* END of implementation of "circle" geometry callback.
267 **************************************************************************
268 *************************************************************************/
269 
270 #include <assert.h>
271 #include "tcl.h"
272 
273 typedef struct Cube Cube;
274 struct Cube {
275   double x;
276   double y;
277   double z;
278   double width;
279   double height;
280   double depth;
281 };
282 
283 static void cube_context_free(void *p){
284   sqlite3_free(p);
285 }
286 
287 /*
288 ** The context pointer registered along with the 'cube' callback is
289 ** always ((void *)&gHere). This is just to facilitate testing, it is not
290 ** actually used for anything.
291 */
292 static int gHere = 42;
293 
294 /*
295 ** Implementation of a simple r-tree geom callback to test for intersection
296 ** of r-tree rows with a "cube" shape. Cubes are defined by six scalar
297 ** coordinates as follows:
298 **
299 **   cube(x, y, z, width, height, depth)
300 **
301 ** The width, height and depth parameters must all be greater than zero.
302 */
303 static int cube_geom(
304   sqlite3_rtree_geometry *p,
305   int nCoord,
306   sqlite3_rtree_dbl *aCoord,
307   int *piRes
308 ){
309   Cube *pCube = (Cube *)p->pUser;
310 
311   assert( p->pContext==(void *)&gHere );
312 
313   if( pCube==0 ){
314     if( p->nParam!=6 || nCoord!=6
315      || p->aParam[3]<=0.0 || p->aParam[4]<=0.0 || p->aParam[5]<=0.0
316     ){
317       return SQLITE_ERROR;
318     }
319     pCube = (Cube *)sqlite3_malloc(sizeof(Cube));
320     if( !pCube ){
321       return SQLITE_NOMEM;
322     }
323     pCube->x = p->aParam[0];
324     pCube->y = p->aParam[1];
325     pCube->z = p->aParam[2];
326     pCube->width = p->aParam[3];
327     pCube->height = p->aParam[4];
328     pCube->depth = p->aParam[5];
329 
330     p->pUser = (void *)pCube;
331     p->xDelUser = cube_context_free;
332   }
333 
334   assert( nCoord==6 );
335   *piRes = 0;
336   if( aCoord[0]<=(pCube->x+pCube->width)
337    && aCoord[1]>=pCube->x
338    && aCoord[2]<=(pCube->y+pCube->height)
339    && aCoord[3]>=pCube->y
340    && aCoord[4]<=(pCube->z+pCube->depth)
341    && aCoord[5]>=pCube->z
342   ){
343     *piRes = 1;
344   }
345 
346   return SQLITE_OK;
347 }
348 #endif /* SQLITE_ENABLE_RTREE */
349 
350 static int register_cube_geom(
351   void * clientData,
352   Tcl_Interp *interp,
353   int objc,
354   Tcl_Obj *CONST objv[]
355 ){
356 #ifndef SQLITE_ENABLE_RTREE
357   UNUSED_PARAMETER(clientData);
358   UNUSED_PARAMETER(interp);
359   UNUSED_PARAMETER(objc);
360   UNUSED_PARAMETER(objv);
361 #else
362   extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
363   extern const char *sqlite3ErrName(int);
364   sqlite3 *db;
365   int rc;
366 
367   if( objc!=2 ){
368     Tcl_WrongNumArgs(interp, 1, objv, "DB");
369     return TCL_ERROR;
370   }
371   if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
372   rc = sqlite3_rtree_geometry_callback(db, "cube", cube_geom, (void *)&gHere);
373   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
374 #endif
375   return TCL_OK;
376 }
377 
378 static int register_circle_geom(
379   void * clientData,
380   Tcl_Interp *interp,
381   int objc,
382   Tcl_Obj *CONST objv[]
383 ){
384 #ifndef SQLITE_ENABLE_RTREE
385   UNUSED_PARAMETER(clientData);
386   UNUSED_PARAMETER(interp);
387   UNUSED_PARAMETER(objc);
388   UNUSED_PARAMETER(objv);
389 #else
390   extern int getDbPointer(Tcl_Interp*, const char*, sqlite3**);
391   extern const char *sqlite3ErrName(int);
392   sqlite3 *db;
393   int rc;
394 
395   if( objc!=2 ){
396     Tcl_WrongNumArgs(interp, 1, objv, "DB");
397     return TCL_ERROR;
398   }
399   if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
400   rc = sqlite3_rtree_geometry_callback(db, "circle", circle_geom, 0);
401   if( rc==SQLITE_OK ){
402     rc = sqlite3_rtree_query_callback(db, "Qcircle",
403                                       circle_query_func, 0, 0);
404   }
405   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
406 #endif
407   return TCL_OK;
408 }
409 
410 int Sqlitetestrtree_Init(Tcl_Interp *interp){
411   Tcl_CreateObjCommand(interp, "register_cube_geom", register_cube_geom, 0, 0);
412   Tcl_CreateObjCommand(interp, "register_circle_geom",register_circle_geom,0,0);
413   return TCL_OK;
414 }
415