pgRouting

root/tags/release-1.0-beta/astar.c

Revision 39, 14.9 kB (checked in by anton, 1 year ago)

1.0.0b tag added

Line 
1 /*
2  * A* Shortest path algorithm for PostgreSQL
3  *
4  * Copyright (c) 2006 Anton A. Patrushev, Orkney, Inc.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  */
21
22 #include "postgres.h"
23 #include "executor/spi.h"
24 #include "funcapi.h"
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <search.h>
29
30 #include "astar.h"
31
32 //-------------------------------------------------------------------------
33
34 /*
35  * Define this to have profiling enabled
36  */
37 //#define PROFILE
38
39 #ifdef PROFILE
40 #include <sys/time.h>
41
42 struct timeval prof_astar, prof_store, prof_extract, prof_total;
43 long proftime[5];
44 long profipts1, profipts2, profopts;
45
46 #define profstart(x) do { gettimeofday(&x, NULL); } while (0);
47 #define profstop(n, x) do { struct timeval _profstop;   \
48         long _proftime;                         \
49         gettimeofday(&_profstop, NULL);                         \
50         _proftime = ( _profstop.tv_sec*1000000+_profstop.tv_usec) -     \
51                 ( x.tv_sec*1000000+x.tv_usec); \
52         elog(NOTICE, \
53                 "PRF(%s) %lu (%f ms)", \
54                 (n), \
55              _proftime, _proftime / 1000.0);    \
56         } while (0);
57
58 #else
59
60 #define profstart(x) do { } while (0);
61 #define profstop(n, x) do { } while (0);
62
63 #endif // PROFILE
64
65
66 //-------------------------------------------------------------------------
67
68 Datum shortest_path_astar(PG_FUNCTION_ARGS);
69
70 #undef DEBUG
71 //#define DEBUG 1
72
73 #ifdef DEBUG
74 #define DBG(format, arg...)                     \
75     elog(NOTICE, format , ## arg)
76 #else
77 #define DBG(format, arg...) do { ; } while (0)
78 #endif
79
80 // The number of tuples to fetch from the SPI cursor at each iteration
81 #define TUPLIMIT 1000
82
83 static char *
84 text2char(text *in)
85 {
86   char *out = palloc(VARSIZE(in));
87
88   memcpy(out, VARDATA(in), VARSIZE(in) - VARHDRSZ);
89   out[VARSIZE(in) - VARHDRSZ] = '\0';
90   return out;
91 }
92
93 static int
94 finish(int code, int ret)
95 {
96   code = SPI_finish();
97   if (code  != SPI_OK_FINISH )
98     {
99       elog(ERROR,"couldn't disconnect from SPI");
100       return -1 ;
101     }
102  
103   return ret;
104 }
105  
106 typedef struct edge_astar_columns
107 {
108   int id;
109   int source;
110   int target;
111   int cost;
112   int reverse_cost;
113   int s_x;
114   int s_y;
115   int t_x;
116   int t_y;
117 } edge_astar_columns_t;
118
119 static int
120 fetch_edge_astar_columns(SPITupleTable *tuptable,
121                          edge_astar_columns_t *edge_columns,
122                          bool has_reverse_cost)
123 {
124   edge_columns->id = SPI_fnumber(SPI_tuptable->tupdesc, "id");
125   edge_columns->source = SPI_fnumber(SPI_tuptable->tupdesc, "source");
126   edge_columns->target = SPI_fnumber(SPI_tuptable->tupdesc, "target");
127   edge_columns->cost = SPI_fnumber(SPI_tuptable->tupdesc, "cost");
128   if (edge_columns->id == SPI_ERROR_NOATTRIBUTE ||
129       edge_columns->source == SPI_ERROR_NOATTRIBUTE ||
130       edge_columns->target == SPI_ERROR_NOATTRIBUTE ||
131       edge_columns->cost == SPI_ERROR_NOATTRIBUTE)
132     {
133       elog(ERROR, "Error, query must return columns "
134            "'id', 'source', 'target' and 'cost'");
135       return -1;
136     }
137
138   if (SPI_gettypeid(SPI_tuptable->tupdesc,
139                     edge_columns->source) != INT4OID ||
140       SPI_gettypeid(SPI_tuptable->tupdesc,
141                     edge_columns->target) != INT4OID ||
142       SPI_gettypeid(SPI_tuptable->tupdesc, edge_columns->cost) != FLOAT8OID)
143     {
144       elog(ERROR, "Error, columns 'source', 'target' must be of type int4, "
145            "'cost' must be of type float8");
146       return -1;
147     }
148
149   DBG("columns: id %i source %i target %i cost %i",
150       edge_columns->id, edge_columns->source,
151       edge_columns->target, edge_columns->cost);
152
153   if (has_reverse_cost)
154     {
155       edge_columns->reverse_cost = SPI_fnumber(SPI_tuptable->tupdesc,
156                                                "reverse_cost");
157
158       if (edge_columns->reverse_cost == SPI_ERROR_NOATTRIBUTE)
159         {
160           elog(ERROR, "Error, reverse_cost is used, but query did't return "
161                "'reverse_cost' column");
162           return -1;
163         }
164
165       if (SPI_gettypeid(SPI_tuptable->tupdesc,
166                         edge_columns->reverse_cost) != FLOAT8OID)
167         {
168           elog(ERROR, "Error, columns 'reverse_cost' must be of type float8");
169           return -1;
170         }
171
172       DBG("columns: reverse_cost cost %i", edge_columns->reverse_cost);
173     }
174
175   edge_columns->s_x = SPI_fnumber(SPI_tuptable->tupdesc, "x1");
176   edge_columns->s_y = SPI_fnumber(SPI_tuptable->tupdesc, "y1");
177   edge_columns->t_x = SPI_fnumber(SPI_tuptable->tupdesc, "x2");
178   edge_columns->t_y = SPI_fnumber(SPI_tuptable->tupdesc, "y2");
179
180   if (edge_columns->s_x == SPI_ERROR_NOATTRIBUTE ||
181       edge_columns->s_y == SPI_ERROR_NOATTRIBUTE ||
182       edge_columns->t_x == SPI_ERROR_NOATTRIBUTE ||
183       edge_columns->t_y == SPI_ERROR_NOATTRIBUTE)
184     {
185       elog(ERROR, "Error, query must return columns "
186            "'x1', 'x2', 'y1' and 'y2'");
187       return -1;
188     }
189
190   DBG("columns: x1 %i y1 %i x2 %i y2 %i",
191       edge_columns->s_x, edge_columns->s_y,
192       edge_columns->t_x,edge_columns->t_y);
193    
194   return 0;
195 }
196
197 static void
198 fetch_edge_astar(HeapTuple *tuple, TupleDesc *tupdesc,
199                  edge_astar_columns_t *edge_columns,
200                  edge_astar_t *target_edge)
201 {
202   Datum binval;
203   bool isnull;
204
205   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->id, &isnull);
206   if (isnull)
207     elog(ERROR, "id contains a null value");
208   target_edge->id = DatumGetInt32(binval);
209
210   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->source, &isnull);
211   if (isnull)
212     elog(ERROR, "source contains a null value");
213   target_edge->source = DatumGetInt32(binval);
214
215   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->target, &isnull);
216   if (isnull)
217     elog(ERROR, "target contains a null value");
218   target_edge->target = DatumGetInt32(binval);
219
220   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->cost, &isnull);
221   if (isnull)
222     elog(ERROR, "cost contains a null value");
223   target_edge->cost = DatumGetFloat8(binval);
224
225   if (edge_columns->reverse_cost != -1)
226     {
227       binval = SPI_getbinval(*tuple, *tupdesc,
228                              edge_columns->reverse_cost, &isnull);
229       if (isnull)
230         elog(ERROR, "reverse_cost contains a null value");
231       target_edge->reverse_cost =  DatumGetFloat8(binval);
232     }
233
234   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->s_x, &isnull);
235   if (isnull)
236     elog(ERROR, "source x contains a null value");
237   target_edge->s_x = DatumGetFloat8(binval);
238
239   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->s_y, &isnull);
240   if (isnull)
241     elog(ERROR, "source y contains a null value");
242   target_edge->s_y = DatumGetFloat8(binval);
243  
244   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->t_x, &isnull);
245   if (isnull)
246     elog(ERROR, "target x contains a null value");
247   target_edge->t_x = DatumGetFloat8(binval);
248  
249   binval = SPI_getbinval(*tuple, *tupdesc, edge_columns->t_y, &isnull);
250   if (isnull)
251     elog(ERROR, "target y contains a null value");
252   target_edge->t_y = DatumGetFloat8(binval);
253 }
254
255
256 static int compute_shortest_path_astar(char* sql, int source_vertex_id,
257                                        int target_vertex_id, bool directed,
258                                        bool has_reverse_cost,
259                                        path_element_t **path, int *path_count)
260 {
261  
262   int SPIcode;
263   void *SPIplan;
264   Portal SPIportal;
265   bool moredata = TRUE;
266   int ntuples;
267   edge_astar_t *edges = NULL;
268   int total_tuples = 0;
269  
270   int v_max_id=0;
271   int v_min_id=INT_MAX; 
272    
273   edge_astar_columns_t edge_columns = {id: -1, source: -1, target: -1,
274                                        cost: -1, reverse_cost: -1,
275                                        s_x: -1, s_y: -1, t_x: -1, t_y: -1};
276   char *err_msg;
277   int ret = -1;
278   register int z;
279  
280   int s_count=0;
281   int t_count=0;
282  
283   struct vItem
284   {
285     int id;
286     int key;
287   };
288  
289   DBG("start shortest_path_astar\n");
290        
291   SPIcode = SPI_connect();
292   if (SPIcode  != SPI_OK_CONNECT)
293     {
294       elog(ERROR, "shortest_path_astar: couldn't open a connection to SPI");
295       return -1;
296     }
297
298   SPIplan = SPI_prepare(sql, 0, NULL);
299   if (SPIplan  == NULL)
300     {
301       elog(ERROR, "shortest_path_astar: couldn't create query plan via SPI");
302       return -1;
303     }
304
305   if ((SPIportal = SPI_cursor_open(NULL, SPIplan, NULL, NULL, true)) == NULL)
306     {
307       elog(ERROR, "shortest_path_astar: SPI_cursor_open('%s') returns NULL",
308            sql);
309       return -1;
310     }
311
312   while (moredata == TRUE)
313     {
314       SPI_cursor_fetch(SPIportal, TRUE, TUPLIMIT);
315
316       if (edge_columns.id == -1)
317         {
318           if (fetch_edge_astar_columns(SPI_tuptable, &edge_columns,
319                                        has_reverse_cost) == -1)
320             return finish(SPIcode, ret);
321         }
322
323       ntuples = SPI_processed;
324       total_tuples += ntuples;
325       if (!edges)
326         edges = palloc(total_tuples * sizeof(edge_astar_t));
327       else
328         edges = repalloc(edges, total_tuples * sizeof(edge_astar_t));
329
330       if (edges == NULL)
331         {
332           elog(ERROR, "Out of memory");
333           return finish(SPIcode, ret);
334         }
335
336       if (ntuples > 0)
337         {
338           int t;
339           SPITupleTable *tuptable = SPI_tuptable;
340           TupleDesc tupdesc = SPI_tuptable->tupdesc;
341          
342           for (t = 0; t < ntuples; t++)
343             {
344               HeapTuple tuple = tuptable->vals[t];
345               fetch_edge_astar(&tuple, &tupdesc, &edge_columns,
346                                &edges[total_tuples - ntuples + t]);
347             }
348           SPI_freetuptable(tuptable);
349         }
350       else
351         {
352           moredata = FALSE;
353         }
354     }
355    
356   //defining min and max vertex id
357      
358   DBG("Total %i tuples", total_tuples);
359    
360   for(z=0; z<total_tuples; z++)
361   {
362     if(edges[z].source<v_min_id)
363     v_min_id=edges[z].source;
364  
365     if(edges[z].source>v_max_id)
366       v_max_id=edges[z].source;
367                                    
368     if(edges[z].target<v_min_id)
369       v_min_id=edges[z].target;
370
371     if(edges[z].target>v_max_id)
372       v_max_id=edges[z].target;     
373                                                                        
374     DBG("%i <-> %i", v_min_id, v_max_id);
375                                                                
376   }
377
378   //:::::::::::::::::::::::::::::::::::: 
379   //:: reducing vertex id (renumbering)
380   //::::::::::::::::::::::::::::::::::::
381   for(z=0; z<total_tuples; z++)
382   {
383
384     //check if edges[] contains source and target
385     if(edges[z].source == source_vertex_id ||
386        edges[z].target == source_vertex_id)
387       ++s_count;
388     if(edges[z].source == target_vertex_id ||
389        edges[z].target == target_vertex_id)
390       ++t_count;
391
392     edges[z].source-=v_min_id;
393     edges[z].target-=v_min_id;
394     DBG("%i - %i", edges[z].source, edges[z].target);
395                                                                
396
397   }
398    
399   DBG("Total %i tuples", total_tuples);
400
401   if(s_count == 0)
402   {
403     elog(ERROR, "Start vertex was not found.");
404     return -1;
405   }
406              
407   if(t_count == 0)
408   {
409     elog(ERROR, "Target vertex was not found.");
410     return -1;
411   }
412                            
413   DBG("Total %i tuples", total_tuples);
414
415   profstop("extract", prof_extract);
416   profstart(prof_astar);
417  
418   DBG("Calling boost_astar <%i>\n", total_tuples);
419    
420   ret = boost_astar(edges, total_tuples, source_vertex_id-v_min_id,
421                     target_vertex_id-v_min_id,
422                     directed, has_reverse_cost,
423                     path, path_count, &err_msg);
424
425   DBG("SIZE %i\n",*path_count);
426
427   DBG("ret =  %i\n",ret);
428  
429   //::::::::::::::::::::::::::::::::
430   //:: restoring original vertex id
431   //::::::::::::::::::::::::::::::::
432   for(z=0;z<*path_count;z++)
433   {
434     //DBG("vetex %i\n",(*path)[z].vertex_id);
435     (*path)[z].vertex_id+=v_min_id;
436   } 
437
438   profstop("astar", prof_astar);
439   profstart(prof_store);
440
441   if (ret < 0)
442     {
443       //elog(ERROR, "Error computing path: %s", err_msg);
444       ereport(ERROR, (errcode(ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED),
445         errmsg("Error computing path: %s", err_msg)));
446     }
447   return finish(SPIcode, ret);
448 }
449
450
451 PG_FUNCTION_INFO_V1(shortest_path_astar);
452 Datum
453 shortest_path_astar(PG_FUNCTION_ARGS)
454 {
455   FuncCallContext     *funcctx;
456   int                  call_cntr;
457   int                  max_calls;
458   TupleDesc            tuple_desc;
459   path_element_t      *path;
460  
461   /* stuff done only on the first call of the function */
462   if (SRF_IS_FIRSTCALL())
463     {
464       MemoryContext   oldcontext;
465       int path_count = 0;
466       int ret;
467
468       // XXX profiling messages are not thread safe
469       profstart(prof_total);
470       profstart(prof_extract);
471      
472       /* create a function context for cross-call persistence */
473       funcctx = SRF_FIRSTCALL_INIT();
474      
475       /* switch to memory context appropriate for multiple function calls */
476       oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
477
478
479       ret = compute_shortest_path_astar(text2char(PG_GETARG_TEXT_P(0)),
480                                         PG_GETARG_INT32(1),
481                                         PG_GETARG_INT32(2),
482                                         PG_GETARG_BOOL(3),
483                                         PG_GETARG_BOOL(4),
484                                         &path, &path_count);
485
486 #ifdef DEBUG
487       DBG("Ret is %i", ret);
488       if (ret >= 0)
489         {
490           int i;
491           for (i = 0; i < path_count; i++)
492             {
493               DBG("Step # %i vertex_id  %i ", i, path[i].vertex_id);
494               DBG("        edge_id    %i ", path[i].edge_id);
495               DBG("        cost       %f ", path[i].cost);
496             }
497         }
498 #endif
499
500       /* total number of tuples to be returned */
501       DBG("Conting tuples number\n");
502       funcctx->max_calls = path_count;
503       funcctx->user_fctx = path;
504      
505       DBG("Path count %i", path_count);
506      
507       funcctx->tuple_desc =
508         BlessTupleDesc(RelationNameGetTupleDesc("path_result"));
509
510       MemoryContextSwitchTo(oldcontext);
511     }
512
513   /* stuff done on every call of the function */
514   DBG("Strange stuff doing\n");
515
516   funcctx = SRF_PERCALL_SETUP();
517  
518   call_cntr = funcctx->call_cntr;
519   max_calls = funcctx->max_calls;
520   tuple_desc = funcctx->tuple_desc;
521   path = (path_element_t*) funcctx->user_fctx;
522  
523   DBG("Trying to allocate some memory\n");
524
525   if (call_cntr < max_calls)    /* do when there is more left to send */
526     {
527       HeapTuple    tuple;
528       Datum        result;
529       Datum *values;
530       char* nulls;
531      
532       DBG("Before allocation\n");
533
534       values = palloc(3 * sizeof(Datum));
535       nulls = palloc(3 * sizeof(char));
536      
537       DBG("After allocation\n");
538      
539       values[0] = Int32GetDatum(path[call_cntr].vertex_id);
540       nulls[0] = ' ';
541       values[1] = Int32GetDatum(path[call_cntr].edge_id);
542       nulls[1] = ' ';
543       values[2] = Float8GetDatum(path[call_cntr].cost);
544       nulls[2] = ' ';
545
546       DBG("Heap making\n");
547      
548       tuple = heap_formtuple(tuple_desc, values, nulls);
549      
550       DBG("Datum making\n");
551      
552       /* make the tuple into a datum */
553       result = HeapTupleGetDatum(tuple);
554      
555
556       DBG("Trying to free some memory\n");
557    
558       /* clean up (this is not really necessary) */
559       pfree(values);
560       pfree(nulls);
561      
562       SRF_RETURN_NEXT(funcctx, result);
563     }
564   else    /* do when there is no more left */
565     {
566       profstop("store", prof_store);
567       profstop("total", prof_total);
568 #ifdef PROFILE
569       elog(NOTICE, "_________");
570 #endif
571       SRF_RETURN_DONE(funcctx);
572     }
573 }
Note: See TracBrowser for help on using the browser.