GRASS 8 Programmer's Manual  8.5.0dev(2025)-9a996ba3cc
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] lengths list of lengths
31  \param[out] ids 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  db_close_cursor(&cursor);
92  return result;
93 }
94 
95 static int cmp_int(const void *a, const void *b)
96 {
97  return *(int *)a - *(int *)b;
98 }
99 
100 /*!
101  \brief Initialises timetable from a database
102 
103  \param In pointer to Map_info structure
104  \param route_layer layer number of routes
105  \param walk_layer layer number of walkers
106  \param route_id id of route
107  \param times list of timestamps
108  \param to_stop ?
109  \param walk_length walk length as string
110  \param timetable pointer to neta_timetable
111  \param route_ids list of route ids
112  \param stop_ids list of stop ids
113 
114  \return 0 on success
115  \return non-zero value on failure
116  */
117 int NetA_init_timetable_from_db(struct Map_info *In, int route_layer,
118  int walk_layer, char *route_id, char *times,
119  char *to_stop, char *walk_length,
120  neta_timetable *timetable, int **route_ids,
121  int **stop_ids)
122 {
123  int more, i, stop, route, time, *stop_pnt, stop1, stop2;
124  dbString sql;
125  dbCursor cursor;
126  dbTable *table;
127  dbColumn *column1, *column2, *column3;
128  dbValue *value;
129  char buf[2000];
130 
131  dbDriver *driver;
132  struct field_info *Fi;
133 
134  Fi = Vect_get_field(In, route_layer);
135  if (Fi == NULL)
136  G_fatal_error(_("Database connection not defined for layer %d"),
137  route_layer);
138 
140  if (driver == NULL)
141  G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
142  Fi->database, Fi->driver);
143 
144  db_init_string(&sql);
145  snprintf(buf, sizeof(buf), "select %s from %s order by %s", route_id,
146  Fi->table, route_id);
147  db_set_string(&sql, buf);
148  timetable->routes =
149  NetA_init_distinct(driver, &sql, &(timetable->route_length), route_ids);
150  if (timetable->routes < 0)
151  return 1;
152 
153  snprintf(buf, sizeof(buf), "select %s from %s order by %s", Fi->key,
154  Fi->table, Fi->key);
155  db_set_string(&sql, buf);
156  timetable->stops =
157  NetA_init_distinct(driver, &sql, &(timetable->stop_length), stop_ids);
158  if (timetable->stops < 0)
159  return 1;
160 
161  timetable->route_stops = (int **)G_calloc(timetable->routes, sizeof(int *));
162  timetable->route_times = (int **)G_calloc(timetable->routes, sizeof(int *));
163  timetable->stop_routes = (int **)G_calloc(timetable->stops, sizeof(int *));
164  timetable->stop_times = (int **)G_calloc(timetable->stops, sizeof(int *));
165  timetable->walk_length = (int *)G_calloc(timetable->stops, sizeof(int));
166  timetable->walk_stops = (int **)G_calloc(timetable->stops, sizeof(int *));
167  timetable->walk_times = (int **)G_calloc(timetable->stops, sizeof(int *));
168  if (!timetable->route_stops || !timetable->route_times ||
169  !timetable->stop_routes || !timetable->stop_times ||
170  !timetable->walk_length) {
171  G_warning(_("Out of memory"));
172  return 2;
173  }
174 
175  for (i = 0; i < timetable->routes; i++) {
176  timetable->route_stops[i] =
177  (int *)G_calloc(timetable->route_length[i], sizeof(int));
178  timetable->route_times[i] =
179  (int *)G_calloc(timetable->route_length[i], sizeof(int));
180  if (!timetable->route_stops[i] || !timetable->route_times[i]) {
181  G_warning(_("Out of memory"));
182  return 2;
183  }
184 
185  timetable->route_length[i] = 0;
186  }
187 
188  for (i = 0; i < timetable->stops; i++) {
189  timetable->stop_routes[i] =
190  (int *)G_calloc(timetable->stop_length[i], sizeof(int));
191  timetable->stop_times[i] =
192  (int *)G_calloc(timetable->stop_length[i], sizeof(int));
193  if (!timetable->stop_routes[i] || !timetable->stop_times[i]) {
194  G_warning(_("Out of memory"));
195  return 2;
196  }
197  timetable->walk_length[i] = 0;
198  timetable->stop_length[i] = 0;
199  }
200 
201  snprintf(buf, sizeof(buf), "select %s, %s, %s from %s order by %s", Fi->key,
202  route_id, times, Fi->table, times);
203  db_set_string(&sql, buf);
204 
205  if (db_open_select_cursor(driver, &sql, &cursor, DB_SEQUENTIAL) != DB_OK) {
206  G_warning(_("Unable to open select cursor: %s"), db_get_string(&sql));
207  return 1;
208  }
209 
210  table = db_get_cursor_table(&cursor);
211  column1 = db_get_table_column(table, 0);
212  column2 = db_get_table_column(table, 1);
213  column3 = db_get_table_column(table, 2);
214  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
215  value = db_get_column_value(column1);
216  stop = db_get_value_int(value);
217  value = db_get_column_value(column2);
218  route = db_get_value_int(value);
219  value = db_get_column_value(column3);
220  time = db_get_value_int(value);
221  stop = (int *)bsearch(&stop, *stop_ids, timetable->stops, sizeof(int),
222  cmp_int) -
223  (*stop_ids);
224  route = (int *)bsearch(&route, *route_ids, timetable->routes,
225  sizeof(int), cmp_int) -
226  (*route_ids);
227 
228  timetable->stop_routes[stop][timetable->stop_length[stop]] = route;
229  timetable->stop_times[stop][timetable->stop_length[stop]++] = time;
230 
231  timetable->route_stops[route][timetable->route_length[route]] = stop;
232  timetable->route_times[route][timetable->route_length[route]++] = time;
233  }
234  db_close_cursor(&cursor);
235 
236  if (walk_layer != -1) {
237 
238  Fi = Vect_get_field(In, walk_layer);
239  snprintf(buf, sizeof(buf), "select %s, %s, %s from %s", Fi->key,
240  to_stop, walk_length, Fi->table);
241  db_set_string(&sql, buf);
242 
243  if (db_open_select_cursor(driver, &sql, &cursor, DB_SEQUENTIAL) !=
244  DB_OK) {
245  G_warning(_("Unable to open select cursor: %s"),
246  db_get_string(&sql));
247  return 1;
248  }
249 
250  table = db_get_cursor_table(&cursor);
251  column1 = db_get_table_column(table, 0);
252  column2 = db_get_table_column(table, 1);
253  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
254  value = db_get_column_value(column2);
255  stop = db_get_value_int(value);
256  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
257  sizeof(int), cmp_int);
258  if (stop_pnt) {
259  value = db_get_column_value(column1);
260  stop = db_get_value_int(value);
261  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
262  sizeof(int), cmp_int);
263  if (stop_pnt) {
264  stop = stop_pnt - (*stop_ids);
265  timetable->walk_length[stop]++;
266  }
267  }
268  }
269  db_close_cursor(&cursor);
270 
271  for (i = 0; i < timetable->stops; i++) {
272  timetable->walk_stops[i] =
273  (int *)G_calloc(timetable->walk_length[i], sizeof(int));
274  timetable->walk_times[i] =
275  (int *)G_calloc(timetable->walk_length[i], sizeof(int));
276  if (!timetable->walk_stops[i] || !timetable->walk_times[i]) {
277  G_warning(_("Out of memory"));
278  return 2;
279  }
280  timetable->walk_length[i] = 0;
281  }
282 
283  if (db_open_select_cursor(driver, &sql, &cursor, DB_SEQUENTIAL) !=
284  DB_OK) {
285  G_warning(_("Unable to open select cursor: %s"),
286  db_get_string(&sql));
287  return 1;
288  }
289 
290  table = db_get_cursor_table(&cursor);
291  column1 = db_get_table_column(table, 0);
292  column2 = db_get_table_column(table, 1);
293  column3 = db_get_table_column(table, 2);
294  while (db_fetch(&cursor, DB_NEXT, &more) == DB_OK && more) {
295  value = db_get_column_value(column2);
296  stop = db_get_value_int(value);
297  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
298  sizeof(int), cmp_int);
299  if (stop_pnt) {
300  stop2 = stop_pnt - (*stop_ids);
301  value = db_get_column_value(column1);
302  stop = db_get_value_int(value);
303  stop_pnt = (int *)bsearch(&stop, *stop_ids, timetable->stops,
304  sizeof(int), cmp_int);
305  if (stop_pnt) {
306  stop1 = stop_pnt - (*stop_ids);
307  value = db_get_column_value(column3);
308  time = db_get_value_int(value);
309  timetable
310  ->walk_stops[stop1][timetable->walk_length[stop1]] =
311  stop2;
312  timetable
313  ->walk_times[stop1][timetable->walk_length[stop1]++] =
314  time;
315  }
316  }
317  }
318  db_close_cursor(&cursor);
319  }
322 
323  return 0;
324 }
325 
326 typedef struct {
327  int v;
328  int conns;
329 } neta_heap_data;
330 
331 static neta_heap_data *new_heap_data(int conns, int v)
332 {
333  neta_heap_data *d = (neta_heap_data *)G_calloc(1, sizeof(neta_heap_data));
334  d->v = v;
335  d->conns = conns;
336  return d;
337 }
338 
339 /*!
340  \brief Update Dijkstra structures
341 
342  \param old_conns old connection
343  \param new_conns new connection
344  \param to old 'to' node
345  \param new_dst new 'to' node
346  \param v ?
347  \param route id of route
348  \param rows ? (unused)
349  \param update ?
350  \param[out] result pointer to neta_timetable_result structure
351  \param heap ?
352  */
353 void NetA_update_dijkstra(int old_conns, int new_conns, int to, int new_dst,
354  int v, int route, int rows UNUSED, int update,
355  neta_timetable_result *result, dglHeap_s *heap)
356 {
357  if (result->dst[new_conns][to] == -1 ||
358  result->dst[new_conns][to] > new_dst) {
359  result->dst[new_conns][to] = new_dst;
360  result->prev_stop[new_conns][to] = v;
361  result->prev_route[new_conns][to] = route;
362  result->prev_conn[new_conns][to] = old_conns;
363  if (update) {
364  dglHeapData_u heap_data;
365 
366  heap_data.pv = (void *)new_heap_data(new_conns, to);
367  dglHeapInsertMin(heap, new_dst, ' ', heap_data);
368  }
369  }
370 }
371 
372 /*!
373  \brief Computes the earliest arrival time.
374 
375  Computes the earliest arrival time to to_stop from from_stop
376  starting at start_time, or -1 if no path exists
377 
378  \param timetable pointer to neta_timetable structure
379  \param from_stop 'from' node
380  \param to_stop 'to' stop
381  \param start_time start timestamp
382  \param min_change ?
383  \param max_changes ?
384  \param walking_change ?
385  \param[out] result pointer to neta_timetable_result
386 
387  \return ?
388  \return -1 on error
389  */
390 int NetA_timetable_shortest_path(neta_timetable *timetable, int from_stop,
391  int to_stop, int start_time, int min_change,
392  int max_changes, int walking_change,
393  neta_timetable_result *result)
394 {
395  int i, j;
396  dglHeap_s heap;
397 
398  int opt_conns, rows = 1;
399 
400  if (max_changes != -1)
401  rows = max_changes + 2;
402 
403  result->rows = rows;
404  result->dst = (int **)G_calloc(rows, sizeof(int *));
405  result->prev_stop = (int **)G_calloc(rows, sizeof(int *));
406  result->prev_route = (int **)G_calloc(rows, sizeof(int *));
407  result->prev_conn = (int **)G_calloc(rows, sizeof(int *));
408 
409  if (!result->dst || !result->prev_stop || !result->prev_route ||
410  !result->prev_conn) {
411  G_warning(_("Out of memory"));
412  return -1;
413  }
414 
415  for (i = 0; i < rows; i++) {
416  result->dst[i] = (int *)G_calloc(timetable->stops, sizeof(int));
417  result->prev_stop[i] = (int *)G_calloc(timetable->stops, sizeof(int));
418  result->prev_route[i] = (int *)G_calloc(timetable->stops, sizeof(int));
419  result->prev_conn[i] = (int *)G_calloc(timetable->stops, sizeof(int));
420  if (!result->dst[i] || !result->prev_stop[i] ||
421  !result->prev_route[i] || !result->prev_conn[i]) {
422  G_warning(_("Out of memory"));
423  return -1;
424  }
425  }
426 
427  if (from_stop == to_stop) {
428  result->dst[0][to_stop] = start_time;
429  result->prev_route[0][to_stop] = result->prev_stop[0][to_stop] = -1;
430  result->routes = 0;
431  return start_time;
432  }
433 
434  result->routes = -1;
435  if (walking_change > 1)
436  walking_change = 1;
437  if (walking_change < 0 || max_changes == -1)
438  walking_change = 0;
439  dglHeapInit(&heap);
440 
441  for (i = 0; i < rows; i++)
442  for (j = 0; j < timetable->stops; j++)
443  result->dst[i][j] = result->prev_stop[i][j] =
444  result->prev_route[i][j] = -1;
445 
446  result->dst[0][from_stop] = start_time - min_change;
447  result->prev_stop[0][from_stop] = result->prev_route[0][from_stop] = -1;
448  dglHeapData_u heap_data;
449 
450  heap_data.pv = (void *)new_heap_data(0, from_stop);
451  dglHeapInsertMin(&heap, start_time - min_change, ' ', heap_data);
452 
453  while (1) {
454  dglInt32_t v, dist, conns;
455  dglHeapNode_s heap_node;
456  int new_conns, walk_conns, update;
457 
458  if (!dglHeapExtractMin(&heap, &heap_node))
459  break;
460  v = ((neta_heap_data *)(heap_node.value.pv))->v;
461  conns = ((neta_heap_data *)(heap_node.value.pv))->conns;
462  dist = heap_node.key;
463 
464  if (dist > result->dst[conns][v])
465  continue;
466  if (v == to_stop)
467  break;
468  new_conns = (max_changes == -1) ? 0 : (conns + 1);
469  walk_conns = conns + walking_change;
470 
471  /*walking */
472  if (walk_conns < rows) {
473  /* update = (max_changes == -1 || walk_conns <=
474  * max_changes); */
475  update = 1;
476  for (i = 0; i < timetable->walk_length[v]; i++) {
477  int to = timetable->walk_stops[v][i];
478  int new_dst = dist + timetable->walk_times[v][i];
479 
480  NetA_update_dijkstra(conns, walk_conns, to, new_dst, v, -2,
481  rows, update, result, &heap);
482  }
483  }
484 
485  if (new_conns >= rows)
486  continue;
487  /*process all routes arriving after dist+min_change */
488  for (i = 0; i < timetable->stop_length[v]; i++)
489  if (timetable->stop_times[v][i] >= dist + min_change) {
490  int route = timetable->stop_routes[v][i];
491 
492  /*find the index of v on the route */
493  for (j = 0; j < timetable->route_length[route]; j++)
494  if (timetable->route_stops[route][j] == v)
495  break;
496  j++;
497  for (; j < timetable->route_length[route]; j++) {
498  int to = timetable->route_stops[route][j];
499 
500  NetA_update_dijkstra(conns, new_conns, to,
501  timetable->route_times[route][j], v,
502  route, rows, 1, result, &heap);
503  }
504  }
505  }
506  dglHeapFree(&heap, NULL);
507  opt_conns = -1;
508  for (i = 0; i < rows; i++)
509  if (result->dst[i][to_stop] != -1 &&
510  (opt_conns == -1 ||
511  result->dst[opt_conns][to_stop] > result->dst[i][to_stop]))
512  opt_conns = i;
513  result->routes = opt_conns;
514 
515  if (opt_conns != -1)
516  return result->dst[opt_conns][to_stop];
517  return -1;
518 }
519 
520 /*!
521  \brief Get time
522 
523  Get time when route "route" arrives at stop "stop" or -1.
524 
525  \param timetable pointer to neta_timetable structure
526  \param stop 'stop' node id
527  \param route route id
528 
529  \return time
530  \return -1 if not found
531  */
533  int route)
534 {
535  int i;
536 
537  for (i = 0; i < timetable->route_length[route]; i++)
538  if (timetable->route_stops[route][i] == stop)
539  return timetable->route_times[route][i];
540  return -1;
541 }
542 
543 /*!
544  \brief Free neta_timetable_result structure
545 
546  \param result pointer to neta_timetable_result structure
547  */
549 {
550  int i;
551 
552  for (i = 0; i < result->rows; i++) {
553  G_free(result->dst[i]);
554  G_free(result->prev_stop[i]);
555  G_free(result->prev_route[i]);
556  }
557  G_free(result->dst);
558  G_free(result->prev_stop);
559  G_free(result->prev_route);
560 }
#define NULL
Definition: ccmath.h:32
Main header of GRASS DataBase Management Interface.
#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:147
#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:46
#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:27
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:117
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:548
void NetA_update_dijkstra(int old_conns, int new_conns, int to, int new_dst, int v, int route, int rows, int update, neta_timetable_result *result, dglHeap_s *heap)
Update Dijkstra structures.
Definition: timetables.c:353
int NetA_timetable_get_route_time(neta_timetable *timetable, int stop, int route)
Get time.
Definition: timetables.c:532
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:390
long dglInt32_t
Definition: type.h:36
void * pv
Definition: heap.h:26