GRASS GIS 8 Programmer's Manual  8.5.0dev(2024)-d6dec75dd4
timetables.c
Go to the documentation of this file.
1 /*!
2  \file vector/neta/timetables.c
3 
4  \brief Network Analysis library - timetables
5 
6  Shortest path using timetables.
7 
8  (C) 2009-2010 by Daniel Bundala, and the GRASS Development Team
9 
10  This program is free software under the GNU General Public License
11  (>=v2). Read the file COPYING that comes with GRASS for details.
12 
13  \author Daniel Bundala (Google Summer of Code 2009)
14  */
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <grass/gis.h>
19 #include <grass/vector.h>
20 #include <grass/dbmi.h>
21 #include <grass/glocale.h>
22 #include <grass/dgl/graph.h>
23 #include <grass/neta.h>
24 
25 /*!
26  \brief Get number of distinct elements
27 
28  \param driver DB driver
29  \param sql SQl string
30  \param[out] list of lengths
31  \param[out] list of ids
32 
33  \return number of distinct elements
34  \return -1 on failure
35  */
36 int NetA_init_distinct(dbDriver *driver, dbString *sql, int **lengths,
37  int **ids)
38 {
39  int count, last, cur, result, index, more;
40  dbCursor cursor;
41  dbTable *table;
42  dbColumn *column;
43  dbValue *value;
44 
45  if (db_open_select_cursor(driver, sql, &cursor, DB_SEQUENTIAL) != DB_OK) {
46  G_warning(_("Unable to open select cursor: %s"), db_get_string(sql));
47  return -1;
48  }
49  /*TODO: check column types */
50 
51  count = last = 0;
52  /*count number of distinct routes */
53  table = db_get_cursor_table(&cursor);
54  column = db_get_table_column(table, 0);
55  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
56  value = db_get_column_value(column);
57  cur = db_get_value_int(value);
58  if (count == 0 || cur != last) {
59  last = cur;
60  count++;
61  }
62  }
63  result = count;
64  db_close_cursor(&cursor);
65 
66  *lengths = (int *)G_calloc(count, sizeof(int));
67  *ids = (int *)G_calloc(count, sizeof(int));
68  if (!*lengths || !*ids) {
69  G_warning(_("Out of memory"));
70  return -1;
71  }
72  if (db_open_select_cursor(driver, sql, &cursor, DB_SEQUENTIAL) != DB_OK) {
73  G_warning(_("Unable to open select cursor: %s"), db_get_string(sql));
74  return -1;
75  }
76  count = index = 0;
77  /*calculate the lengths of the routes */
78  table = db_get_cursor_table(&cursor);
79  column = db_get_table_column(table, 0);
80  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
81  value = db_get_column_value(column);
82  cur = db_get_value_int(value);
83  if (count != 0 && cur != last)
84  index++;
85  if (count == 0 || cur != last)
86  (*ids)[index] = cur;
87  (*lengths)[index]++;
88  last = cur;
89  count++;
90  }
91  return result;
92 }
93 
94 static int cmp_int(const void *a, const void *b)
95 {
96  return *(int *)a - *(int *)b;
97 }
98 
99 /*!
100  \brief Initialises timetable from a database
101 
102  \param In pointer to Map_info structure
103  \param route_layer layer number of routes
104  \param walk_layer layer number of walkers
105  \param route_id id of route
106  \param times list of timestamps
107  \param to_stop ?
108  \param walk_length walk length as string
109  \param timetable pointer to neta_timetable
110  \param route_ids list of route ids
111  \param stop_ids list of stop ids
112 
113  \return 0 on success
114  \return non-zero value on failure
115  */
116 int NetA_init_timetable_from_db(struct Map_info *In, int route_layer,
117  int walk_layer, char *route_id, char *times,
118  char *to_stop, char *walk_length,
119  neta_timetable *timetable, int **route_ids,
120  int **stop_ids)
121 {
122  int more, i, stop, route, time, *stop_pnt, stop1, stop2;
123  dbString sql;
124  dbCursor cursor;
125  dbTable *table;
126  dbColumn *column1, *column2, *column3;
127  dbValue *value;
128  char buf[2000];
129 
130  dbDriver *driver;
131  struct field_info *Fi;
132 
133  Fi = Vect_get_field(In, route_layer);
134  if (Fi == NULL)
135  G_fatal_error(_("Database connection not defined for layer %d"),
136  route_layer);
137 
139  if (driver == NULL)
140  G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
141  Fi->database, Fi->driver);
142 
143  db_init_string(&sql);
144  sprintf(buf, "select %s from %s order by %s", route_id, Fi->table,
145  route_id);
146  db_set_string(&sql, buf);
147  timetable->routes =
148  NetA_init_distinct(driver, &sql, &(timetable->route_length), route_ids);
149  if (timetable->routes < 0)
150  return 1;
151 
152  sprintf(buf, "select %s from %s order by %s", Fi->key, Fi->table, Fi->key);
153  db_set_string(&sql, buf);
154  timetable->stops =
155  NetA_init_distinct(driver, &sql, &(timetable->stop_length), stop_ids);
156  if (timetable->stops < 0)
157  return 1;
158 
159  timetable->route_stops = (int **)G_calloc(timetable->routes, sizeof(int *));
160  timetable->route_times = (int **)G_calloc(timetable->routes, sizeof(int *));
161  timetable->stop_routes = (int **)G_calloc(timetable->stops, sizeof(int *));
162  timetable->stop_times = (int **)G_calloc(timetable->stops, sizeof(int *));
163  timetable->walk_length = (int *)G_calloc(timetable->stops, sizeof(int));
164  timetable->walk_stops = (int **)G_calloc(timetable->stops, sizeof(int *));
165  timetable->walk_times = (int **)G_calloc(timetable->stops, sizeof(int *));
166  if (!timetable->route_stops || !timetable->route_times ||
167  !timetable->stop_routes || !timetable->stop_times ||
168  !timetable->walk_length) {
169  G_warning(_("Out of memory"));
170  return 2;
171  }
172 
173  for (i = 0; i < timetable->routes; i++) {
174  timetable->route_stops[i] =
175  (int *)G_calloc(timetable->route_length[i], sizeof(int));
176  timetable->route_times[i] =
177  (int *)G_calloc(timetable->route_length[i], sizeof(int));
178  if (!timetable->route_stops[i] || !timetable->route_times[i]) {
179  G_warning(_("Out of memory"));
180  return 2;
181  }
182 
183  timetable->route_length[i] = 0;
184  }
185 
186  for (i = 0; i < timetable->stops; i++) {
187  timetable->stop_routes[i] =
188  (int *)G_calloc(timetable->stop_length[i], sizeof(int));
189  timetable->stop_times[i] =
190  (int *)G_calloc(timetable->stop_length[i], sizeof(int));
191  if (!timetable->stop_routes[i] || !timetable->stop_times[i]) {
192  G_warning(_("Out of memory"));
193  return 2;
194  }
195  timetable->walk_length[i] = 0;
196  timetable->stop_length[i] = 0;
197  }
198 
199  sprintf(buf, "select %s, %s, %s from %s order by %s", Fi->key, route_id,
200  times, Fi->table, times);
201  db_set_string(&sql, buf);
202 
203  if (db_open_select_cursor(driver, &sql, &cursor, DB_SEQUENTIAL) != DB_OK) {
204  G_warning(_("Unable to open select cursor: %s"), db_get_string(&sql));
205  return 1;
206  }
207 
208  table = db_get_cursor_table(&cursor);
209  column1 = db_get_table_column(table, 0);
210  column2 = db_get_table_column(table, 1);
211  column3 = db_get_table_column(table, 2);
212  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
213  value = db_get_column_value(column1);
214  stop = db_get_value_int(value);
215  value = db_get_column_value(column2);
216  route = db_get_value_int(value);
217  value = db_get_column_value(column3);
218  time = db_get_value_int(value);
219  stop = (int *)bsearch(&stop, *stop_ids, timetable->stops, sizeof(int),
220  cmp_int) -
221  (*stop_ids);
222  route = (int *)bsearch(&route, *route_ids, timetable->routes,
223  sizeof(int), cmp_int) -
224  (*route_ids);
225 
226  timetable->stop_routes[stop][timetable->stop_length[stop]] = route;
227  timetable->stop_times[stop][timetable->stop_length[stop]++] = time;
228 
229  timetable->route_stops[route][timetable->route_length[route]] = stop;
230  timetable->route_times[route][timetable->route_length[route]++] = time;
231  }
232  db_close_cursor(&cursor);
233 
234  if (walk_layer != -1) {
235 
236  Fi = Vect_get_field(In, walk_layer);
237  sprintf(buf, "select %s, %s, %s from %s", Fi->key, to_stop, walk_length,
238  Fi->table);
239  db_set_string(&sql, buf);
240 
241  if (db_open_select_cursor(driver, &sql, &cursor, DB_SEQUENTIAL) !=
242  DB_OK) {
243  G_warning(_("Unable to open select cursor: %s"),
244  db_get_string(&sql));
245  return 1;
246  }
247 
248  table = db_get_cursor_table(&cursor);
249  column1 = db_get_table_column(table, 0);
250  column2 = db_get_table_column(table, 1);
251  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
252  value = db_get_column_value(column2);
253  stop = db_get_value_int(value);
254  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
255  sizeof(int), cmp_int);
256  if (stop_pnt) {
257  value = db_get_column_value(column1);
258  stop = db_get_value_int(value);
259  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
260  sizeof(int), cmp_int);
261  if (stop_pnt) {
262  stop = stop_pnt - (*stop_ids);
263  timetable->walk_length[stop]++;
264  }
265  }
266  }
267  db_close_cursor(&cursor);
268 
269  for (i = 0; i < timetable->stops; i++) {
270  timetable->walk_stops[i] =
271  (int *)G_calloc(timetable->walk_length[i], sizeof(int));
272  timetable->walk_times[i] =
273  (int *)G_calloc(timetable->walk_length[i], sizeof(int));
274  if (!timetable->walk_stops[i] || !timetable->walk_times[i]) {
275  G_warning(_("Out of memory"));
276  return 2;
277  }
278  timetable->walk_length[i] = 0;
279  }
280 
281  if (db_open_select_cursor(driver, &sql, &cursor, DB_SEQUENTIAL) !=
282  DB_OK) {
283  G_warning(_("Unable to open select cursor: %s"),
284  db_get_string(&sql));
285  return 1;
286  }
287 
288  table = db_get_cursor_table(&cursor);
289  column1 = db_get_table_column(table, 0);
290  column2 = db_get_table_column(table, 1);
291  column3 = db_get_table_column(table, 2);
292  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
293  value = db_get_column_value(column2);
294  stop = db_get_value_int(value);
295  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
296  sizeof(int), cmp_int);
297  if (stop_pnt) {
298  stop2 = stop_pnt - (*stop_ids);
299  value = db_get_column_value(column1);
300  stop = db_get_value_int(value);
301  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
302  sizeof(int), cmp_int);
303  if (stop_pnt) {
304  stop1 = stop_pnt - (*stop_ids);
305  value = db_get_column_value(column3);
306  time = db_get_value_int(value);
307  timetable
308  ->walk_stops[stop1][timetable->walk_length[stop1]] =
309  stop2;
310  timetable
311  ->walk_times[stop1][timetable->walk_length[stop1]++] =
312  time;
313  }
314  }
315  }
316  db_close_cursor(&cursor);
317  }
320 
321  return 0;
322 }
323 
324 typedef struct {
325  int v;
326  int conns;
327 } neta_heap_data;
328 
329 static neta_heap_data *new_heap_data(int conns, int v)
330 {
331  neta_heap_data *d = (neta_heap_data *)G_calloc(1, sizeof(neta_heap_data));
332  d->v = v;
333  d->conns = conns;
334  return d;
335 }
336 
337 /*!
338  \brief Update Dijkstra structures
339 
340  \param olc_conns old connection
341  \param new_conns new connection
342  \param to old 'to' node
343  \param new_dst new 'to' node
344  \param v ?
345  \param route id of route
346  \param rows ? (unused)
347  \param update ?
348  \param[out] result pointer to neta_timetable_result structure
349  \param heap ?
350  */
351 void NetA_update_dijkstra(int old_conns, int new_conns, int to, int new_dst,
352  int v, int route, int rows UNUSED, int update,
353  neta_timetable_result *result, dglHeap_s *heap)
354 {
355  if (result->dst[new_conns][to] == -1 ||
356  result->dst[new_conns][to] > new_dst) {
357  result->dst[new_conns][to] = new_dst;
358  result->prev_stop[new_conns][to] = v;
359  result->prev_route[new_conns][to] = route;
360  result->prev_conn[new_conns][to] = old_conns;
361  if (update) {
362  dglHeapData_u heap_data;
363 
364  heap_data.pv = (void *)new_heap_data(new_conns, to);
365  dglHeapInsertMin(heap, new_dst, ' ', heap_data);
366  }
367  }
368 }
369 
370 /*!
371  \brief Computes the earliest arrival time.
372 
373  Computes the earliest arrival time to to_stop from from_stop
374  starting at start_time, or -1 if no path exists
375 
376  \param timetable pointer to neta_timetable structure
377  \param from_stop 'from' node
378  \param to_stop 'to' stop
379  \param start_time start timestamp
380  \param min_change ?
381  \param max_changes ?
382  \param walking_change ?
383  \param[out] result pointer to neta_timetable_result
384 
385  \return ?
386  \return -1 on error
387  */
388 int NetA_timetable_shortest_path(neta_timetable *timetable, int from_stop,
389  int to_stop, int start_time, int min_change,
390  int max_changes, int walking_change,
391  neta_timetable_result *result)
392 {
393  int i, j;
394  dglHeap_s heap;
395 
396  int opt_conns, rows = 1;
397 
398  if (max_changes != -1)
399  rows = max_changes + 2;
400 
401  result->rows = rows;
402  result->dst = (int **)G_calloc(rows, sizeof(int *));
403  result->prev_stop = (int **)G_calloc(rows, sizeof(int *));
404  result->prev_route = (int **)G_calloc(rows, sizeof(int *));
405  result->prev_conn = (int **)G_calloc(rows, sizeof(int *));
406 
407  if (!result->dst || !result->prev_stop || !result->prev_route ||
408  !result->prev_conn) {
409  G_warning(_("Out of memory"));
410  return -1;
411  }
412 
413  for (i = 0; i < rows; i++) {
414  result->dst[i] = (int *)G_calloc(timetable->stops, sizeof(int));
415  result->prev_stop[i] = (int *)G_calloc(timetable->stops, sizeof(int));
416  result->prev_route[i] = (int *)G_calloc(timetable->stops, sizeof(int));
417  result->prev_conn[i] = (int *)G_calloc(timetable->stops, sizeof(int));
418  if (!result->dst[i] || !result->prev_stop[i] ||
419  !result->prev_route[i] || !result->prev_conn[i]) {
420  G_warning(_("Out of memory"));
421  return -1;
422  }
423  }
424 
425  if (from_stop == to_stop) {
426  result->dst[0][to_stop] = start_time;
427  result->prev_route[0][to_stop] = result->prev_stop[0][to_stop] = -1;
428  result->routes = 0;
429  return start_time;
430  }
431 
432  result->routes = -1;
433  if (walking_change > 1)
434  walking_change = 1;
435  if (walking_change < 0 || max_changes == -1)
436  walking_change = 0;
437  dglHeapInit(&heap);
438 
439  for (i = 0; i < rows; i++)
440  for (j = 0; j < timetable->stops; j++)
441  result->dst[i][j] = result->prev_stop[i][j] =
442  result->prev_route[i][j] = -1;
443 
444  result->dst[0][from_stop] = start_time - min_change;
445  result->prev_stop[0][from_stop] = result->prev_route[0][from_stop] = -1;
446  dglHeapData_u heap_data;
447 
448  heap_data.pv = (void *)new_heap_data(0, from_stop);
449  dglHeapInsertMin(&heap, start_time - min_change, ' ', heap_data);
450 
451  while (1) {
452  dglInt32_t v, dist, conns;
453  dglHeapNode_s heap_node;
454  int new_conns, walk_conns, update;
455 
456  if (!dglHeapExtractMin(&heap, &heap_node))
457  break;
458  v = ((neta_heap_data *)(heap_node.value.pv))->v;
459  conns = ((neta_heap_data *)(heap_node.value.pv))->conns;
460  dist = heap_node.key;
461 
462  if (dist > result->dst[conns][v])
463  continue;
464  if (v == to_stop)
465  break;
466  new_conns = (max_changes == -1) ? 0 : (conns + 1);
467  walk_conns = conns + walking_change;
468 
469  /*walking */
470  if (walk_conns < rows) {
471  /* update = (max_changes == -1 || walk_conns <=
472  * max_changes); */
473  update = 1;
474  for (i = 0; i < timetable->walk_length[v]; i++) {
475  int to = timetable->walk_stops[v][i];
476  int new_dst = dist + timetable->walk_times[v][i];
477 
478  NetA_update_dijkstra(conns, walk_conns, to, new_dst, v, -2,
479  rows, update, result, &heap);
480  }
481  }
482 
483  if (new_conns >= rows)
484  continue;
485  /*process all routes arriving after dist+min_change */
486  for (i = 0; i < timetable->stop_length[v]; i++)
487  if (timetable->stop_times[v][i] >= dist + min_change) {
488  int route = timetable->stop_routes[v][i];
489 
490  /*find the index of v on the route */
491  for (j = 0; j < timetable->route_length[route]; j++)
492  if (timetable->route_stops[route][j] == v)
493  break;
494  j++;
495  for (; j < timetable->route_length[route]; j++) {
496  int to = timetable->route_stops[route][j];
497 
498  NetA_update_dijkstra(conns, new_conns, to,
499  timetable->route_times[route][j], v,
500  route, rows, 1, result, &heap);
501  }
502  }
503  }
504  dglHeapFree(&heap, NULL);
505  opt_conns = -1;
506  for (i = 0; i < rows; i++)
507  if (result->dst[i][to_stop] != -1 &&
508  (opt_conns == -1 ||
509  result->dst[opt_conns][to_stop] > result->dst[i][to_stop]))
510  opt_conns = i;
511  result->routes = opt_conns;
512 
513  if (opt_conns != -1)
514  return result->dst[opt_conns][to_stop];
515  return -1;
516 }
517 
518 /*!
519  \brief Get time
520 
521  Get time when route "route" arrives at stop "stop" or -1.
522 
523  \param timetable pointer to neta_timetable structure
524  \param stop 'stop' node id
525  \param route route id
526 
527  \return time
528  \return -1 if not found
529  */
531  int route)
532 {
533  int i;
534 
535  for (i = 0; i < timetable->route_length[route]; i++)
536  if (timetable->route_stops[route][i] == stop)
537  return timetable->route_times[route][i];
538  return -1;
539 }
540 
541 /*!
542  \brief Free neta_timetable_result structure
543 
544  \param result pointer to neta_timetable_result structure
545  */
547 {
548  int i;
549 
550  for (i = 0; i < result->rows; i++) {
551  G_free(result->dst[i]);
552  G_free(result->prev_stop[i]);
553  G_free(result->prev_route[i]);
554  }
555  G_free(result->dst);
556  G_free(result->prev_stop);
557  G_free(result->prev_route);
558 }
#define NULL
Definition: ccmath.h:32
#define DB_SEQUENTIAL
Definition: dbmi.h:123
#define DB_OK
Definition: dbmi.h:71
#define DB_NEXT
Definition: dbmi.h:114
dbValue * db_get_column_value(dbColumn *)
Returns column value for given column structure.
dbColumn * db_get_table_column(dbTable *, int)
Returns column structure for given table and column number.
dbDriver * db_start_driver_open_database(const char *, const char *)
Open driver/database connection.
Definition: db.c:28
dbTable * db_get_cursor_table(dbCursor *)
Get table allocated by cursor.
Definition: cursor.c:67
int db_close_database_shutdown_driver(dbDriver *)
Close driver/database connection.
Definition: db.c:61
int db_set_string(dbString *, const char *)
Inserts string to dbString (enlarge string)
Definition: string.c:41
int db_get_value_int(dbValue *)
Get integer value.
Definition: value.c:38
void db_init_string(dbString *)
Initialize dbString.
Definition: string.c:25
int db_close_cursor(dbCursor *)
Close cursor.
Definition: c_close_cur.c:27
int db_open_select_cursor(dbDriver *, dbString *, dbCursor *, int)
Open select cursor.
Definition: c_openselect.c:37
char * db_get_string(const dbString *)
Get string.
Definition: string.c:140
int db_fetch(dbCursor *, int, int *)
Fetch data from open cursor.
Definition: c_fetch.c:28
void G_free(void *)
Free allocated memory.
Definition: gis/alloc.c:150
#define G_calloc(m, n)
Definition: defs/gis.h:95
void void void void G_fatal_error(const char *,...) __attribute__((format(printf
void G_warning(const char *,...) __attribute__((format(printf
struct field_info * Vect_get_field(struct Map_info *, int)
Get information about link to database (by layer number)
Definition: field.c:515
void Vect_destroy_field_info(struct field_info *)
Free a struct field_info and all memory associated with it.
Definition: field.c:633
const struct driver * driver
Definition: driver/init.c:25
#define UNUSED
A macro for an attribute, if attached to a variable, indicating that the variable is not used.
Definition: gis.h:47
#define _(str)
Definition: glocale.h:10
void dglHeapInit(dglHeap_s *pheap)
Definition: heap.c:27
int dglHeapInsertMin(dglHeap_s *pheap, long key, unsigned char flags, dglHeapData_u value)
Definition: heap.c:50
void dglHeapFree(dglHeap_s *pheap, dglHeapCancelItem_fn pfnCancelItem)
Definition: heap.c:35
int dglHeapExtractMin(dglHeap_s *pheap, dglHeapNode_s *pnoderet)
Definition: heap.c:76
int count
double b
Definition: r_raster.c:39
Vector map info.
Definition: dig_structs.h:1243
long key
Definition: heap.h:35
dglHeapData_u value
Definition: heap.h:36
Definition: heap.h:41
Definition: driver.h:21
Layer (old: field) information.
Definition: dig_structs.h:131
char * table
Name of DB table.
Definition: dig_structs.h:151
char * driver
Name of DB driver ('sqlite', 'dbf', ...)
Definition: dig_structs.h:143
char * database
Definition: dig_structs.h:147
char * key
Name of key column (usually 'cat')
Definition: dig_structs.h:155
int ** route_times
Definition: defs/neta.h:59
int * route_length
Definition: defs/neta.h:56
int ** walk_times
Definition: defs/neta.h:70
int ** stop_times
Definition: defs/neta.h:65
int ** walk_stops
Definition: defs/neta.h:69
int ** stop_routes
Definition: defs/neta.h:63
int * stop_length
Definition: defs/neta.h:62
int * walk_length
Definition: defs/neta.h:67
int ** route_stops
Definition: defs/neta.h:58
int NetA_init_timetable_from_db(struct Map_info *In, int route_layer, int walk_layer, char *route_id, char *times, char *to_stop, char *walk_length, neta_timetable *timetable, int **route_ids, int **stop_ids)
Initialises timetable from a database.
Definition: timetables.c:116
int NetA_init_distinct(dbDriver *driver, dbString *sql, int **lengths, int **ids)
Get number of distinct elements.
Definition: timetables.c:36
void NetA_timetable_result_release(neta_timetable_result *result)
Free neta_timetable_result structure.
Definition: timetables.c:546
int NetA_timetable_get_route_time(neta_timetable *timetable, int stop, int route)
Get time.
Definition: timetables.c:530
int NetA_timetable_shortest_path(neta_timetable *timetable, int from_stop, int to_stop, int start_time, int min_change, int max_changes, int walking_change, neta_timetable_result *result)
Computes the earliest arrival time.
Definition: timetables.c:388
void NetA_update_dijkstra(int old_conns, int new_conns, int to, int new_dst, int v, int route, int rows UNUSED, int update, neta_timetable_result *result, dglHeap_s *heap)
Update Dijkstra structures.
Definition: timetables.c:351
long dglInt32_t
Definition: type.h:36
void * pv
Definition: heap.h:26