main.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. /****************************************************************************
  2. *
  3. * MODULE: r.terraflow
  4. *
  5. * COPYRIGHT (C) 2007, 2010 Laura Toma
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. *****************************************************************************/
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <assert.h>
  22. #include <ctype.h>
  23. #include <time.h>
  24. #include <sys/types.h>
  25. #ifdef HAVE_STATVFS_H
  26. #include <sys/statvfs.h>
  27. #endif
  28. extern "C" {
  29. #include <grass/gis.h>
  30. #include <grass/raster.h>
  31. #include <grass/glocale.h>
  32. }
  33. #include "option.h"
  34. #include "common.h" /* declares the globals */
  35. #include "fill.h"
  36. #include "flow.h"
  37. #include "nodata.h"
  38. #include "grass2str.h"
  39. #include "water.h"
  40. #include "sortutils.h"
  41. /* globals: in common.H
  42. extern statsRecorder *stats;
  43. extern userOptions* opt;
  44. extern struct Cell_head region;
  45. */
  46. /* #define JUMP2FLOW */
  47. /* define it only if you want to skip the flow direction computation
  48. and jump directly to computing flow accumulation; the flowstream
  49. must exist in /STREAM_DIR/flowStream */
  50. /* ---------------------------------------------------------------------- */
  51. void
  52. parse_args(int argc, char *argv[]) {
  53. /* input elevation grid */
  54. struct Option *input_elev;
  55. input_elev = G_define_standard_option(G_OPT_R_ELEV);
  56. /* output filled elevation grid */
  57. struct Option *output_elev;
  58. output_elev = G_define_standard_option(G_OPT_R_OUTPUT);
  59. output_elev->key = "filled";
  60. output_elev->description= _("Name for output filled (flooded) elevation raster map");
  61. output_elev->required = NO;
  62. output_elev->guisection = _("Outputs");
  63. /* output direction grid */
  64. struct Option *output_dir;
  65. output_dir = G_define_standard_option(G_OPT_R_OUTPUT);
  66. output_dir->key = "direction";
  67. output_dir->description= _("Name for output flow direction raster map");
  68. output_dir->required = NO;
  69. output_dir->guisection = _("Outputs");
  70. /* output sinkwatershed grid */
  71. struct Option *output_watershed;
  72. output_watershed = G_define_standard_option(G_OPT_R_OUTPUT);
  73. output_watershed->key = "swatershed";
  74. output_watershed->description= _("Name for output sink-watershed raster map");
  75. output_watershed->required = NO;
  76. output_watershed->guisection = _("Outputs");
  77. /* output flow accumulation grid */
  78. struct Option *output_accu;
  79. output_accu = G_define_standard_option(G_OPT_R_OUTPUT);
  80. output_accu->key = "accumulation";
  81. output_accu->description= _("Name for output flow accumulation raster map");
  82. output_accu->required = NO;
  83. output_accu->guisection = _("Outputs");
  84. #ifdef OUTPUT_TCI
  85. struct Option *output_tci;
  86. output_tci = G_define_standard_option(G_OPT_R_OUTPUT);
  87. output_tci->key = "tci";
  88. output_tci->description=
  89. _("Name for output topographic convergence index (tci) raster map");
  90. output_tci->required = NO;
  91. output_tci->guisection = _("Outputs");
  92. #endif
  93. /* MFD/SFD flag */
  94. struct Flag *sfd_flag;
  95. sfd_flag = G_define_flag() ;
  96. sfd_flag->key = 's';
  97. sfd_flag->label= _("SFD (D8) flow (default is MFD)");
  98. sfd_flag->description =
  99. _("SFD: single flow direction, MFD: multiple flow direction");
  100. /* D8CUT value*/
  101. struct Option *d8cut;
  102. d8cut = G_define_option();
  103. d8cut->key = "d8cut";
  104. d8cut->type = TYPE_DOUBLE;
  105. d8cut->required = NO;
  106. d8cut->label = _("Routing using SFD (D8) direction");
  107. d8cut->description =
  108. _("If flow accumulation is larger than this value it is routed using "
  109. "SFD (D8) direction (meaningfull only for MFD flow). "
  110. "If no answer is given it defaults to infinity.");
  111. /* main memory */
  112. struct Option *mem;
  113. mem = G_define_option() ;
  114. mem->key = "memory";
  115. mem->type = TYPE_INTEGER;
  116. mem->required = NO;
  117. mem->answer = (char *) "300";
  118. mem->description = _("Maximum memory to be used (in MB)");
  119. /* temporary STREAM path */
  120. struct Option *streamdir;
  121. streamdir = G_define_option() ;
  122. streamdir->key = "directory";
  123. streamdir->type = TYPE_STRING;
  124. streamdir->required = NO;
  125. //streamdir->answer = "";
  126. streamdir->description=
  127. _("Directory to hold temporary files (they can be large)");
  128. /* stats file */
  129. struct Option *stats_opt;
  130. stats_opt = G_define_option() ;
  131. stats_opt->key = "stats";
  132. stats_opt->type = TYPE_STRING;
  133. stats_opt->required = NO;
  134. stats_opt->description= _("Name for output file containing runtime statistics");
  135. stats_opt->guisection = _("Outputs");
  136. G_option_requires(input_elev, output_elev, output_dir, output_watershed,
  137. output_accu, output_tci, NULL);
  138. if (G_parser(argc, argv)) {
  139. exit (EXIT_FAILURE);
  140. }
  141. /* ************************* */
  142. assert(opt);
  143. opt->elev_grid = input_elev->answer;
  144. opt->filled_grid = output_elev->answer;
  145. opt->dir_grid = output_dir->answer;
  146. opt->watershed_grid = output_watershed->answer;
  147. opt->flowaccu_grid = output_accu->answer;
  148. #ifdef OUTPUT_TCI
  149. opt->tci_grid = output_tci->answer;
  150. #endif
  151. opt->d8 = sfd_flag->answer;
  152. if (!d8cut->answer) {
  153. opt->d8cut = MAX_ACCU;
  154. } else {
  155. opt->d8cut = atof(d8cut->answer);
  156. }
  157. opt->mem = atoi(mem->answer);
  158. if (!streamdir->answer) {
  159. const char *tmpdir = G_tempfile();
  160. if (G_mkdir(tmpdir) == -1)
  161. G_fatal_error(_("Unable to create temp dir"));
  162. opt->streamdir = G_store(tmpdir);
  163. }
  164. else
  165. opt->streamdir = streamdir->answer;
  166. opt->stats = stats_opt->answer;
  167. opt->verbose = G_verbose() == G_verbose_max();
  168. /* somebody should delete the options */
  169. }
  170. /* ---------------------------------------------------------------------- */
  171. /* check compatibility of map header and region header */
  172. void check_header(char* cellname) {
  173. const char *mapset;
  174. mapset = G_find_raster(cellname, "");
  175. if (mapset == NULL) {
  176. G_fatal_error(_("Raster map <%s> not found"), cellname);
  177. }
  178. /* read cell header */
  179. struct Cell_head cell_hd;
  180. Rast_get_cellhd (cellname, mapset, &cell_hd);
  181. /* check compatibility with module region */
  182. if (!((region->ew_res == cell_hd.ew_res)
  183. && (region->ns_res == cell_hd.ns_res))) {
  184. G_fatal_error(_("Raster map <%s> resolution differs from current region"),
  185. cellname);
  186. } else {
  187. G_verbose_message(_("Header of raster map <%s> compatible with region header"),
  188. cellname);
  189. }
  190. /* check type of input elevation raster and check if precision is lost */
  191. RASTER_MAP_TYPE data_type;
  192. data_type = Rast_map_type(opt->elev_grid, mapset);
  193. #ifdef ELEV_SHORT
  194. G_verbose_message(_("Elevation stored as SHORT (%dB)"),
  195. sizeof(elevation_type));
  196. if (data_type == FCELL_TYPE) {
  197. G_warning(_("Raster map <%s> is of type FCELL_TYPE "
  198. "-- precision may be lost"), opt->elev_grid);
  199. }
  200. if (data_type == DCELL_TYPE) {
  201. G_warning(_("Raster map <%s> is of type DCELL_TYPE "
  202. "-- precision may be lost"), opt->elev_grid);
  203. }
  204. #endif
  205. #ifdef ELEV_FLOAT
  206. G_verbose_message(_("Elevation stored as FLOAT (%ldB)"),
  207. sizeof(elevation_type));
  208. if (data_type == CELL_TYPE) {
  209. G_warning(_("Raster map <%s> is of type CELL_TYPE "
  210. "-- you should use r.terraflow.short"), opt->elev_grid);
  211. }
  212. if (data_type == DCELL_TYPE) {
  213. G_warning(_("Raster map <%s> is of type DCELL_TYPE "
  214. "-- precision may be lost"), opt->elev_grid);
  215. }
  216. #endif
  217. }
  218. /* ---------------------------------------------------------------------- */
  219. void check_args() {
  220. /* check if filled elevation grid name is valid */
  221. if (opt->filled_grid && G_legal_filename (opt->filled_grid) < 0) {
  222. G_fatal_error(_("<%s> is an illegal file name"), opt->filled_grid);
  223. }
  224. /* check if output grid names are valid */
  225. if (opt->dir_grid && G_legal_filename (opt->dir_grid) < 0) {
  226. G_fatal_error(_("<%s> is an illegal file name"), opt->dir_grid);
  227. }
  228. if (opt->flowaccu_grid && G_legal_filename (opt->flowaccu_grid) < 0) {
  229. G_fatal_error(_("<%s> is an illegal file name"), opt->flowaccu_grid);
  230. }
  231. if (opt->watershed_grid && G_legal_filename (opt->watershed_grid) < 0) {
  232. G_fatal_error(_("<%s> is an illegal file name"), opt->watershed_grid);
  233. }
  234. #ifdef OUTPUT_TCI
  235. if (opt->tci_grid && G_legal_filename (opt->tci_grid) < 0) {
  236. G_fatal_error(_("<%s> is an illegal file name"), opt->tci_grid);
  237. }
  238. #endif
  239. /* check compatibility with region */
  240. check_header(opt->elev_grid);
  241. /* what else ? */
  242. }
  243. /* ---------------------------------------------------------------------- */
  244. void record_args(int argc, char **argv) {
  245. time_t t = time(NULL);
  246. char buf[BUFSIZ];
  247. if(t == (time_t)-1) {
  248. perror("time");
  249. exit(1);
  250. }
  251. #ifdef __MINGW32__
  252. strcpy(buf, ctime(&t));
  253. #else
  254. ctime_r(&t, buf);
  255. buf[24] = '\0';
  256. #endif
  257. stats->timestamp(buf);
  258. *stats << "Command Line: " << endl;
  259. for(int i=0; i<argc; i++) {
  260. *stats << argv[i] << " ";
  261. }
  262. *stats << endl;
  263. *stats << "input elevation grid: " << opt->elev_grid << "\n";
  264. if (opt->filled_grid)
  265. *stats << "output (flooded) elevations grid: " << opt->filled_grid << "\n";
  266. if (opt->dir_grid)
  267. *stats << "output directions grid: " << opt->dir_grid << "\n";
  268. if (opt->watershed_grid)
  269. *stats << "output sinkwatershed grid: " << opt->watershed_grid << "\n";
  270. if (opt->flowaccu_grid)
  271. *stats << "output accumulation grid: " << opt->flowaccu_grid << "\n";
  272. #ifdef OUTPUT_TCI
  273. if (opt->tci_grid)
  274. *stats << "output tci grid: " << opt->tci_grid << "\n";
  275. #endif
  276. if (opt->d8) {
  277. stats ->comment("SFD (D8) flow direction");
  278. } else {
  279. stats->comment("MFD flow direction");
  280. }
  281. sprintf(buf, "D8CUT=%f", opt->d8cut);
  282. stats->comment(buf);
  283. size_t mm_size = (size_t) opt->mem << 20; /* (in bytes) */
  284. char tmp[100];
  285. formatNumber(tmp, mm_size);
  286. sprintf(buf, "Memory size: %s bytes", tmp);
  287. stats->comment(buf);
  288. }
  289. /* ---------------------------------------------------------------------- */
  290. void
  291. setFlowAccuColorTable(char* cellname) {
  292. struct Colors colors;
  293. const char *mapset;
  294. struct Range r;
  295. mapset = G_find_raster(cellname, "");
  296. if (mapset == NULL) {
  297. G_fatal_error (_("Raster map <%s> not found"), cellname);
  298. }
  299. if (Rast_read_range(cellname, mapset, &r) == -1) {
  300. G_fatal_error(_("cannot read range"));
  301. }
  302. /*G_debug(1, "%s range is: min=%d, max=%d\n", cellname, r.min, r.max);*/
  303. int v[6];
  304. v[0] = r.min;
  305. v[1] = 5;
  306. v[2] = 30;
  307. v[3] = 100;
  308. v[4] = 1000;
  309. v[5] = r.max;
  310. Rast_init_colors(&colors);
  311. Rast_add_c_color_rule(&v[0], 255,255,255, &v[1], 255,255,0, &colors);
  312. Rast_add_c_color_rule(&v[1], 255,255,0, &v[2], 0,255,255, &colors);
  313. Rast_add_c_color_rule(&v[2], 0,255,255, &v[3], 0,127,255, &colors);
  314. Rast_add_c_color_rule(&v[3], 0,127,255, &v[4], 0,0,255, &colors);
  315. Rast_add_c_color_rule(&v[4], 0,0,255, &v[5], 0,0,0, &colors);
  316. Rast_write_colors(cellname, mapset, &colors);
  317. Rast_free_colors(&colors);
  318. }
  319. /* ---------------------------------------------------------------------- */
  320. void
  321. setSinkWatershedColorTable(char* cellname) {
  322. struct Colors colors;
  323. const char *mapset;
  324. struct Range r;
  325. mapset = G_find_raster(cellname, "");
  326. if (mapset == NULL) {
  327. G_fatal_error (_("Raster map <%s> not found"), cellname);
  328. }
  329. if (Rast_read_range(cellname, mapset, &r) == -1) {
  330. G_fatal_error(_("cannot read range"));
  331. }
  332. Rast_init_colors(&colors);
  333. Rast_make_random_colors(&colors, 1, r.max);
  334. Rast_write_colors(cellname, mapset, &colors);
  335. Rast_free_colors(&colors);
  336. }
  337. /* print the largest interm file that will be generated during
  338. r.terraflow */
  339. void
  340. printMaxSortSize(long nodata_count) {
  341. char buf[BUFSIZ];
  342. long long fillmaxsize = (long long)nrows*ncols*sizeof(waterWindowType);
  343. long long flowmaxsize = (long long)(nrows*ncols - nodata_count)*sizeof(sweepItem);
  344. long long maxneed = (fillmaxsize > flowmaxsize) ? fillmaxsize: flowmaxsize;
  345. maxneed = 2*maxneed; /* need 2*N to sort */
  346. G_debug(1, "total elements=%ld, nodata elements=%ld",
  347. (long)nrows * ncols, nodata_count);
  348. G_debug(1, "largest temporary files: ");
  349. G_debug(1, "\t\t FILL: %s [%ld elements, %ldB each]",
  350. formatNumber(buf, fillmaxsize),
  351. (long)nrows * ncols, sizeof(waterWindowType));
  352. G_debug(1, "\t\t FLOW: %s [%ld elements, %ldB each]",
  353. formatNumber(buf, flowmaxsize),
  354. (long)nrows * ncols - nodata_count, sizeof(sweepItem));
  355. G_debug(1, "Will need at least %s space available in %s",
  356. formatNumber(buf, maxneed), /* need 2*N to sort */
  357. getenv(STREAM_TMPDIR));
  358. #ifdef HAVE_STATVFS_H
  359. G_debug(1, "Checking current space in %s: ", getenv(STREAM_TMPDIR));
  360. struct statvfs statbuf;
  361. statvfs(getenv(STREAM_TMPDIR), &statbuf);
  362. float avail = statbuf.f_bsize*statbuf.f_bavail;
  363. G_debug(1, "available %ld blocks x %ldB = %.0fB",
  364. (long)statbuf.f_bavail, statbuf.f_bsize, avail);
  365. if (avail > maxneed) {
  366. G_debug(1, ". OK.");
  367. } else {
  368. G_fatal_error("Not enough space available");
  369. }
  370. #endif
  371. }
  372. /* ---------------------------------------------------------------------- */
  373. int
  374. main(int argc, char *argv[]) {
  375. struct GModule *module;
  376. Rtimer rtTotal;
  377. char buf[BUFSIZ];
  378. /* initialize GIS library */
  379. G_gisinit(argv[0]);
  380. module = G_define_module();
  381. module->description = _("Performs flow computation for massive grids.");
  382. G_add_keyword(_("raster"));
  383. G_add_keyword(_("hydrology"));
  384. G_add_keyword(_("flow"));
  385. G_add_keyword(_("accumulation"));
  386. G_add_keyword(_("sink"));
  387. /* read user options; fill in global <opt> */
  388. opt = (userOptions*)malloc(sizeof(userOptions));
  389. assert(opt);
  390. region = (struct Cell_head*)malloc(sizeof(struct Cell_head));
  391. assert(region);
  392. parse_args(argc, argv);
  393. /* get the current region and dimensions */
  394. G_get_set_window(region);
  395. check_args();
  396. int nr = Rast_window_rows();
  397. int nc = Rast_window_cols();
  398. if ((nr > dimension_type_max) || (nc > dimension_type_max)) {
  399. G_fatal_error(_("[nrows=%d, ncols=%d] dimension_type overflow -- "
  400. "change dimension_type and recompile"), nr, nc);
  401. } else {
  402. nrows = (dimension_type)nr;
  403. ncols = (dimension_type)nc;
  404. }
  405. G_verbose_message( _("Region size is %d x %d"), nrows, ncols);
  406. /* check STREAM path (the place where intermediate STREAMs are placed) */
  407. sprintf(buf, "%s=%s",STREAM_TMPDIR, opt->streamdir);
  408. /* don't pass an automatic variable; putenv() isn't guaranteed to make a copy */
  409. putenv(G_store(buf));
  410. if (getenv(STREAM_TMPDIR) == NULL) {
  411. G_fatal_error(_("%s not set"), STREAM_TMPDIR);
  412. } else {
  413. G_verbose_message(_("STREAM temporary files in <%s>. "
  414. "THESE INTERMEDIATE STREAMS WILL NOT BE DELETED "
  415. "IN CASE OF ABNORMAL TERMINATION OF THE PROGRAM. "
  416. "TO SAVE SPACE PLEASE DELETE THESE FILES MANUALLY!"),
  417. getenv(STREAM_TMPDIR));
  418. }
  419. if (opt->stats) {
  420. /* open the stats file */
  421. stats = new statsRecorder(opt->stats);
  422. record_args(argc, argv);
  423. {
  424. char buf[BUFSIZ];
  425. long grid_size = nrows * ncols;
  426. *stats << "region size = " << formatNumber(buf, grid_size) << " elts "
  427. << "(" << nrows << " rows x " << ncols << " cols)\n";
  428. stats->flush();
  429. }
  430. }
  431. /* set up STREAM memory manager */
  432. size_t mm_size = (size_t) opt->mem << 20; /* opt->mem is in MB */
  433. MM_manager.set_memory_limit(mm_size);
  434. if (opt->verbose) {
  435. MM_manager.warn_memory_limit();
  436. } else {
  437. MM_manager.ignore_memory_limit();
  438. }
  439. if (opt->verbose)
  440. MM_manager.print_limit_mode();
  441. /* initialize nodata */
  442. nodataType::init();
  443. if (stats)
  444. *stats << "internal nodata value: " << nodataType::ELEVATION_NODATA << endl;
  445. /* start timing -- after parse_args, which are interactive */
  446. rt_start(rtTotal);
  447. #ifndef JUMP2FLOW
  448. /* read elevation into a stream */
  449. AMI_STREAM<elevation_type> *elstr=NULL;
  450. long nodata_count;
  451. elstr = cell2stream<elevation_type>(opt->elev_grid, elevation_type_max,
  452. &nodata_count);
  453. /* print the largest interm file that will be generated */
  454. printMaxSortSize(nodata_count);
  455. /* -------------------------------------------------- */
  456. /* compute flow direction and filled elevation (and watersheds) */
  457. AMI_STREAM<direction_type> *dirstr=NULL;
  458. AMI_STREAM<elevation_type> *filledstr=NULL;
  459. AMI_STREAM<waterWindowBaseType> *flowStream=NULL;
  460. AMI_STREAM<labelElevType> *labeledWater = NULL;
  461. flowStream=computeFlowDirections(elstr, filledstr, dirstr, labeledWater);
  462. delete elstr;
  463. /* write streams to GRASS raster maps */
  464. if (opt->dir_grid)
  465. stream2_CELL(dirstr, nrows, ncols, opt->dir_grid);
  466. delete dirstr;
  467. if (opt->filled_grid) {
  468. #ifdef ELEV_SHORT
  469. stream2_CELL(filledstr, nrows, ncols, opt->filled_grid);
  470. #else
  471. stream2_CELL(filledstr, nrows, ncols, opt->filled_grid,true);
  472. #endif
  473. }
  474. delete filledstr;
  475. if (opt->watershed_grid) {
  476. stream2_CELL(labeledWater, nrows, ncols, labelElevTypePrintLabel(),
  477. opt->watershed_grid);
  478. setSinkWatershedColorTable(opt->watershed_grid);
  479. }
  480. delete labeledWater;
  481. #else
  482. AMI_STREAM<waterWindowBaseType> *flowStream;
  483. char path[GPATH_MAX];
  484. sprintf(path, "%s/flowStream", streamdir->answer);
  485. flowStream = new AMI_STREAM<waterWindowBaseType>(path);
  486. G_verbose_message(_("flowStream opened: len=%d\n", flowStream->stream_len());
  487. G_verbose_message(_("jumping to flow accumulation computation\n");
  488. #endif
  489. /* -------------------------------------------------- */
  490. /* compute flow accumulation (and tci) */
  491. AMI_STREAM<sweepOutput> *outstr=NULL;
  492. computeFlowAccumulation(flowStream, outstr);
  493. /* delete flowStream -- deleted inside */
  494. /* write output stream to GRASS raster maps */
  495. #ifdef OUTPUT_TCI
  496. if (opt->flowaccu_grid && opt->tci_grid)
  497. stream2_FCELL(outstr, nrows, ncols, printAccumulation(), printTci(),
  498. opt->flowaccu_grid, opt->tci_grid);
  499. else if (opt->tci_grid)
  500. stream2_FCELL(outstr, nrows, ncols, printTci(),
  501. opt->tci_grid);
  502. else if (opt->flowaccu_grid)
  503. stream2_FCELL(outstr, nrows, ncols, printAccumulation(),
  504. opt->flowaccu_grid);
  505. #else
  506. if (opt->flowaccu_grid)
  507. stream2_FCELL(outstr, nrows, ncols, printAccumulation(),
  508. opt->flowaccu_grid);
  509. #endif
  510. if (opt->flowaccu_grid)
  511. setFlowAccuColorTable(opt->flowaccu_grid);
  512. delete outstr;
  513. rt_stop(rtTotal);
  514. if (stats) {
  515. stats->recordTime("Total running time: ", rtTotal);
  516. stats->timestamp("end");
  517. }
  518. G_done_msg(" ");
  519. /* free the globals */
  520. free(region);
  521. free(opt);
  522. if (stats)
  523. delete stats;
  524. return 0;
  525. }