main.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. /****************************************************************************
  2. *
  3. * MODULE: r.in.bin
  4. * AUTHOR(S): Jacques Bouchard, France (bouchard@onera.fr)
  5. * Bob Covill <bcovill tekmap.ns.ca>
  6. * Markus Metz
  7. * PURPOSE: Import binary files
  8. * COPYRIGHT: (C) 2000 - 2014 by the GRASS Development Team
  9. *
  10. * This program is free software under the GNU General Public
  11. * License (>=v2). Read the file COPYING that comes with GRASS
  12. * for details.
  13. *
  14. *****************************************************************************/
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #include <unistd.h>
  18. #include <math.h>
  19. #include <sys/stat.h>
  20. #include <grass/gis.h>
  21. #include <grass/raster.h>
  22. #include <grass/glocale.h>
  23. #include "gmt_grd.h"
  24. enum fliphv {
  25. FLIP_H = 1,
  26. FLIP_V = 2,
  27. };
  28. static void swap_2(void *p)
  29. {
  30. unsigned char *q = p;
  31. unsigned char t;
  32. t = q[0]; q[0] = q[1]; q[1] = t;
  33. }
  34. static void swap_4(void *p)
  35. {
  36. unsigned char *q = p;
  37. unsigned char t;
  38. t = q[0]; q[0] = q[3]; q[3] = t;
  39. t = q[1]; q[1] = q[2]; q[2] = t;
  40. }
  41. static void swap_8(void *p)
  42. {
  43. unsigned char *q = p;
  44. unsigned char t;
  45. t = q[0]; q[0] = q[7]; q[7] = t;
  46. t = q[1]; q[1] = q[6]; q[6] = t;
  47. t = q[2]; q[2] = q[5]; q[5] = t;
  48. t = q[3]; q[3] = q[4]; q[4] = t;
  49. }
  50. static void read_int(FILE *fp, int swap_flag, int *x)
  51. {
  52. if (fread(x, 4, 1, fp) != 1)
  53. G_fatal_error(_("Error reading data"));
  54. if (swap_flag)
  55. swap_4(x);
  56. }
  57. static void read_double(FILE *fp, int swap_flag, double *x)
  58. {
  59. if (fread(x, 8, 1, fp) != 1)
  60. G_fatal_error(_("Error reading data"));
  61. if (swap_flag)
  62. swap_8(x);
  63. }
  64. static void read_gmt_header(struct GRD_HEADER *header, int swap_flag, FILE *fp)
  65. {
  66. read_int(fp, swap_flag, &header->nx);
  67. read_int(fp, swap_flag, &header->ny);
  68. read_int(fp, swap_flag, &header->node_offset);
  69. read_double(fp, swap_flag, &header->x_min);
  70. read_double(fp, swap_flag, &header->x_max);
  71. read_double(fp, swap_flag, &header->y_min);
  72. read_double(fp, swap_flag, &header->y_max);
  73. read_double(fp, swap_flag, &header->z_min);
  74. read_double(fp, swap_flag, &header->z_max);
  75. read_double(fp, swap_flag, &header->x_inc);
  76. read_double(fp, swap_flag, &header->y_inc);
  77. read_double(fp, swap_flag, &header->z_scale_factor);
  78. read_double(fp, swap_flag, &header->z_add_offset);
  79. fread(&header->x_units, sizeof(char[GRD_UNIT_LEN]), 1, fp);
  80. fread(&header->y_units, sizeof(char[GRD_UNIT_LEN]), 1, fp);
  81. fread(&header->z_units, sizeof(char[GRD_UNIT_LEN]), 1, fp);
  82. fread(&header->title, sizeof(char[GRD_TITLE_LEN]), 1, fp);
  83. fread(&header->command, sizeof(char[GRD_COMMAND_LEN]), 1, fp);
  84. fread(&header->remark, sizeof(char[GRD_REMARK_LEN]), 1, fp);
  85. }
  86. static void get_gmt_header(const struct GRD_HEADER *header, struct Cell_head *region)
  87. {
  88. region->cols = header->nx;
  89. region->rows = header->ny;
  90. region->west = header->x_min;
  91. region->east = header->x_max;
  92. region->south = header->y_min;
  93. region->north = header->y_max;
  94. region->ew_res = header->x_inc;
  95. region->ns_res = header->y_inc;
  96. }
  97. static void convert_cell(
  98. DCELL *out_cell, unsigned char *in_cell,
  99. int is_fp, int is_signed, int bytes, int swap_flag)
  100. {
  101. if (swap_flag) {
  102. switch (bytes) {
  103. case 1: break;
  104. case 2: swap_2(in_cell); break;
  105. case 4: swap_4(in_cell); break;
  106. case 8: swap_8(in_cell); break;
  107. }
  108. }
  109. if (is_fp) {
  110. switch (bytes) {
  111. case 4:
  112. *out_cell = (DCELL) *(float *) in_cell;
  113. break;
  114. case 8:
  115. *out_cell = (DCELL) *(double *) in_cell;
  116. break;
  117. }
  118. }
  119. else if (is_signed) {
  120. switch (bytes) {
  121. case 1:
  122. *out_cell = (DCELL) *(signed char *) in_cell;
  123. break;
  124. case 2:
  125. *out_cell = (DCELL) *(short *) in_cell;
  126. break;
  127. case 4:
  128. *out_cell = (DCELL) *(int *) in_cell;
  129. break;
  130. #ifdef HAVE_LONG_LONG_INT
  131. case 8:
  132. *out_cell = (DCELL) *(long long *) in_cell;
  133. break;
  134. #endif
  135. }
  136. }
  137. else {
  138. switch (bytes) {
  139. case 1:
  140. *out_cell = (DCELL) *(unsigned char *) in_cell;
  141. break;
  142. case 2:
  143. *out_cell = (DCELL) *(unsigned short *) in_cell;
  144. break;
  145. case 4:
  146. *out_cell = (DCELL) *(unsigned int *) in_cell;
  147. break;
  148. #ifdef HAVE_LONG_LONG_INT
  149. case 8:
  150. *out_cell = (DCELL) *(unsigned long long *) in_cell;
  151. break;
  152. #endif
  153. }
  154. }
  155. }
  156. static void convert_row(
  157. DCELL *raster, unsigned char *in_buf, int ncols,
  158. int is_fp, int is_signed, int bytes, int swap_flag,
  159. double null_val, int flip)
  160. {
  161. unsigned char *ptr = in_buf;
  162. int i, i2;
  163. for (i = 0; i < ncols; i++) {
  164. DCELL x;
  165. convert_cell(&x, ptr, is_fp, is_signed, bytes, swap_flag);
  166. i2 = i;
  167. if (flip & FLIP_H)
  168. i2 = ncols - i - 1;
  169. if (x == null_val)
  170. Rast_set_d_null_value(&raster[i2], 1);
  171. else
  172. raster[i2] = x;
  173. ptr += bytes;
  174. }
  175. }
  176. int main(int argc, char *argv[])
  177. {
  178. struct GModule *module;
  179. struct
  180. {
  181. struct Option *input;
  182. struct Option *output;
  183. struct Option *null;
  184. struct Option *bytes;
  185. struct Option *hbytes;
  186. struct Option *bands;
  187. struct Option *order;
  188. struct Option *title;
  189. struct Option *north;
  190. struct Option *south;
  191. struct Option *east;
  192. struct Option *west;
  193. struct Option *rows;
  194. struct Option *cols;
  195. struct Option *flip;
  196. } parm;
  197. struct
  198. {
  199. struct Flag *float_in;
  200. struct Flag *double_in;
  201. struct Flag *gmt_hd;
  202. struct Flag *sign;
  203. struct Flag *swap;
  204. } flag;
  205. char *desc = NULL;
  206. const char *input;
  207. const char *outpre;
  208. char output[GNAME_MAX];
  209. const char *title;
  210. double null_val = 0.0/0.0;
  211. int is_fp;
  212. int is_signed;
  213. int bytes, hbytes;
  214. int band, nbands, bsize;
  215. int order;
  216. int swap_flag;
  217. int i, flip;
  218. struct Cell_head cellhd;
  219. int nrows, ncols;
  220. int grass_nrows, grass_ncols;
  221. unsigned char *in_buf;
  222. DCELL *out_buf;
  223. RASTER_MAP_TYPE map_type;
  224. int fd;
  225. FILE *fp;
  226. off_t file_size, band_off;
  227. struct GRD_HEADER header;
  228. int row;
  229. struct History history;
  230. off_t expected;
  231. G_gisinit(argv[0]);
  232. /* Set description */
  233. module = G_define_module();
  234. G_add_keyword(_("raster"));
  235. G_add_keyword(_("import"));
  236. module->description =
  237. _("Import a binary raster file into a GRASS raster map layer.");
  238. flag.float_in = G_define_flag();
  239. flag.float_in->key = 'f';
  240. flag.float_in->description =
  241. _("Import as floating-point data (default: integer)");
  242. flag.double_in = G_define_flag();
  243. flag.double_in->key = 'd';
  244. flag.double_in->description =
  245. _("Import as double-precision floating-point data (default: integer)");
  246. flag.sign = G_define_flag();
  247. flag.sign->key = 's';
  248. flag.sign->description = _("Signed data (two's complement)");
  249. flag.sign->guisection = _("Settings");
  250. flag.swap = G_define_flag();
  251. flag.swap->key = 'b';
  252. flag.swap->description = _("Byte swap the data during import");
  253. flag.swap->guisection = _("Settings");
  254. flag.gmt_hd = G_define_flag();
  255. flag.gmt_hd->key = 'h';
  256. flag.gmt_hd->description = _("Get region info from GMT style header");
  257. flag.gmt_hd->guisection = _("Bounds");
  258. parm.input = G_define_standard_option(G_OPT_F_BIN_INPUT);
  259. parm.input->description = _("Name of binary raster file to be imported");
  260. parm.output = G_define_standard_option(G_OPT_R_OUTPUT);
  261. parm.output->description = _("Output name or prefix if several bands are imported");
  262. parm.title = G_define_option();
  263. parm.title->key = "title";
  264. parm.title->key_desc = "phrase";
  265. parm.title->type = TYPE_STRING;
  266. parm.title->required = NO;
  267. parm.title->description = _("Title for resultant raster map");
  268. parm.bytes = G_define_option();
  269. parm.bytes->key = "bytes";
  270. parm.bytes->type = TYPE_INTEGER;
  271. parm.bytes->required = NO;
  272. parm.bytes->options = "1,2,4,8";
  273. parm.bytes->description = _("Number of bytes per cell");
  274. parm.bytes->guisection = _("Settings");
  275. parm.hbytes = G_define_option();
  276. parm.hbytes->key = "header";
  277. parm.hbytes->type = TYPE_INTEGER;
  278. parm.hbytes->required = NO;
  279. parm.hbytes->answer = "0";
  280. parm.hbytes->description = _("Header size in bytes");
  281. parm.hbytes->guisection = _("Settings");
  282. parm.bands = G_define_option();
  283. parm.bands->key = "bands";
  284. parm.bands->type = TYPE_INTEGER;
  285. parm.bands->required = NO;
  286. parm.bands->answer = "1";
  287. parm.bands->label = _("Number of bands in input file");
  288. parm.bands->description = _("Bands must be in band-sequential order");
  289. parm.bands->guisection = _("Settings");
  290. parm.order = G_define_option();
  291. parm.order->key = "order";
  292. parm.order->type = TYPE_STRING;
  293. parm.order->required = NO;
  294. parm.order->options = "big,little,native,swap";
  295. parm.order->description = _("Output byte order");
  296. parm.order->answer = "native";
  297. parm.north = G_define_option();
  298. parm.north->key = "north";
  299. parm.north->type = TYPE_DOUBLE;
  300. parm.north->required = NO;
  301. parm.north->description =
  302. _("Northern limit of geographic region (outer edge)");
  303. parm.north->guisection = _("Bounds");
  304. parm.south = G_define_option();
  305. parm.south->key = "south";
  306. parm.south->type = TYPE_DOUBLE;
  307. parm.south->required = NO;
  308. parm.south->description =
  309. _("Southern limit of geographic region (outer edge)");
  310. parm.south->guisection = _("Bounds");
  311. parm.east = G_define_option();
  312. parm.east->key = "east";
  313. parm.east->type = TYPE_DOUBLE;
  314. parm.east->required = NO;
  315. parm.east->description =
  316. _("Eastern limit of geographic region (outer edge)");
  317. parm.east->guisection = _("Bounds");
  318. parm.west = G_define_option();
  319. parm.west->key = "west";
  320. parm.west->type = TYPE_DOUBLE;
  321. parm.west->required = NO;
  322. parm.west->description =
  323. _("Western limit of geographic region (outer edge)");
  324. parm.west->guisection = _("Bounds");
  325. parm.rows = G_define_option();
  326. parm.rows->key = "rows";
  327. parm.rows->type = TYPE_INTEGER;
  328. parm.rows->required = NO;
  329. parm.rows->description = _("Number of rows");
  330. parm.rows->guisection = _("Bounds");
  331. parm.cols = G_define_option();
  332. parm.cols->key = "cols";
  333. parm.cols->type = TYPE_INTEGER;
  334. parm.cols->required = NO;
  335. parm.cols->description = _("Number of columns");
  336. parm.cols->guisection = _("Bounds");
  337. parm.null = G_define_option();
  338. parm.null->key = "anull";
  339. parm.null->type = TYPE_DOUBLE;
  340. parm.null->required = NO;
  341. parm.null->description = _("Set Value to NULL");
  342. parm.null->guisection = _("Settings");
  343. parm.flip = G_define_option();
  344. parm.flip->key = "flip";
  345. parm.flip->type = TYPE_STRING;
  346. parm.flip->required = NO;
  347. parm.flip->options = "h,v";
  348. parm.flip->multiple = YES;
  349. parm.flip->label = _("Flip input horizontal and/or vertical");
  350. G_asprintf(&desc,
  351. "h;%s;v;%s",
  352. _("Flip input horizontal (East - West)"),
  353. _("Flip input vertical (North - South)"));
  354. parm.flip->descriptions = desc;
  355. parm.flip->guisection = _("Settings");
  356. if (G_parser(argc, argv))
  357. exit(EXIT_FAILURE);
  358. input = parm.input->answer;
  359. outpre = parm.output->answer;
  360. title = parm.title->answer;
  361. nbands = atoi(parm.bands->answer);
  362. if (nbands < 1)
  363. G_fatal_error(_("Option %s must be > 0"), parm.bands->key);
  364. hbytes = atoi(parm.hbytes->answer);
  365. if (hbytes < 0)
  366. G_fatal_error(_("Option %s must be >= 0"), parm.hbytes->key);
  367. if (G_strcasecmp(parm.order->answer, "big") == 0)
  368. order = 0;
  369. else if (G_strcasecmp(parm.order->answer, "little") == 0)
  370. order = 1;
  371. else if (G_strcasecmp(parm.order->answer, "native") == 0)
  372. order = G_is_little_endian() ? 1 : 0;
  373. else if (G_strcasecmp(parm.order->answer, "swap") == 0)
  374. order = G_is_little_endian() ? 0 : 1;
  375. if (flag.swap->answer) {
  376. if (strcmp(parm.order->answer, "native") != 0)
  377. G_fatal_error(_("-%c and %s= are mutually exclusive"),
  378. flag.swap->key, parm.order->key);
  379. order = G_is_little_endian() ? 0 : 1;
  380. }
  381. if (flag.gmt_hd->answer && parm.flip->answer)
  382. G_fatal_error(_("-%c and %s= are mutually exclusive"),
  383. flag.gmt_hd->key, parm.flip->key);
  384. if (flag.gmt_hd->answer && hbytes > 0)
  385. G_warning(_("Option %s= is ignored if -%c is set"),
  386. parm.hbytes->key, flag.gmt_hd->key);
  387. if (flag.gmt_hd->answer && nbands > 1)
  388. G_warning(_("Option %s= is ignored if -%c is set"),
  389. parm.bands->key, flag.gmt_hd->key);
  390. swap_flag = order == (G_is_little_endian() ? 0 : 1);
  391. is_signed = !!flag.sign->answer;
  392. flip = 0;
  393. if (parm.flip->answers) {
  394. for (i = 0; parm.flip->answers[i]; i++) {
  395. if (parm.flip->answers[i][0] == 'h')
  396. flip |= FLIP_H;
  397. if (parm.flip->answers[i][0] == 'v')
  398. flip |= FLIP_V;
  399. }
  400. }
  401. is_fp = 0;
  402. bytes = 0;
  403. if (parm.bytes->answer)
  404. bytes = atoi(parm.bytes->answer);
  405. if (flag.float_in->answer && flag.double_in->answer)
  406. G_fatal_error(_("-%c and -%c are mutually exclusive"),
  407. flag.float_in->key, flag.double_in->key);
  408. if (flag.float_in->answer) {
  409. if (bytes && bytes < 4)
  410. G_fatal_error(_("-%c incompatible with %s=%d; must be 4 or 8"),
  411. flag.float_in->key, parm.bytes->key, bytes);
  412. if (!bytes)
  413. bytes = 4;
  414. is_fp = 1;
  415. }
  416. if (flag.double_in->answer) {
  417. if (bytes && bytes != 8)
  418. G_fatal_error(_("-%c incompatible with %s=%d; must be 8"),
  419. flag.double_in->key, parm.bytes->key, bytes);
  420. if (!bytes)
  421. bytes = 8;
  422. is_fp = 1;
  423. }
  424. if (!is_fp && !bytes)
  425. G_fatal_error(_("%s= required for integer data"), parm.bytes->key);
  426. #ifndef HAVE_LONG_LONG_INT
  427. if (!is_fp && bytes > 4)
  428. G_fatal_error(_("Integer input doesn't support %s=8 in this build"),
  429. parm.bytes->key);
  430. #endif
  431. if (bytes != 1 && bytes != 2 && bytes != 4 && bytes != 8)
  432. G_fatal_error(_("%s= must be 1, 2, 4 or 8"), parm.bytes->key);
  433. if (parm.null->answer && G_strcasecmp(parm.null->answer, "nan") != 0)
  434. null_val = atof(parm.null->answer);
  435. cellhd.zone = G_zone();
  436. cellhd.proj = G_projection();
  437. if (!flag.gmt_hd->answer) {
  438. /* NO GMT header */
  439. int num_bounds;
  440. if (!parm.rows->answer || !parm.cols->answer)
  441. G_fatal_error(_("Either -%c or %s= and %s= must be given"),
  442. flag.gmt_hd->key, parm.rows->key, parm.cols->key);
  443. num_bounds = !!parm.north->answer + !!parm.south->answer +
  444. !!parm.east->answer + !!parm.west->answer;
  445. if (num_bounds != 0 && num_bounds != 4)
  446. G_fatal_error(_("Either all or none of %s=, %s=, %s= and %s= must be given"),
  447. parm.north->key, parm.south->key,
  448. parm.east->key, parm.west->key);
  449. cellhd.rows = atoi(parm.rows->answer);
  450. cellhd.cols = atoi(parm.cols->answer);
  451. if (num_bounds > 0) {
  452. if (!G_scan_northing(parm.north->answer, &cellhd.north, cellhd.proj))
  453. G_fatal_error(_("Illegal north coordinate <%s>"), parm.north->answer);
  454. if (!G_scan_northing(parm.south->answer, &cellhd.south, cellhd.proj))
  455. G_fatal_error(_("Illegal south coordinate <%s>"), parm.south->answer);
  456. if (!G_scan_easting(parm.east->answer, &cellhd.east, cellhd.proj))
  457. G_fatal_error(_("Illegal east coordinate <%s>"), parm.east->answer);
  458. if (!G_scan_easting(parm.west->answer, &cellhd.west, cellhd.proj))
  459. G_fatal_error(_("Illegal west coordinate <%s>"), parm.west->answer);
  460. }
  461. }
  462. fp = fopen(input, "rb");
  463. if (!fp)
  464. G_fatal_error(_("Unable to open <%s>"), input);
  465. /* Find File Size in Byte and Check against byte size */
  466. G_fseek(fp, 0, SEEK_END);
  467. file_size = G_ftell(fp);
  468. G_fseek(fp, 0, SEEK_SET);
  469. /* Read binary GMT style header */
  470. if (flag.gmt_hd->answer) {
  471. read_gmt_header(&header, swap_flag, fp);
  472. get_gmt_header(&header, &cellhd);
  473. hbytes = 892;
  474. nbands = 1;
  475. }
  476. /* Adjust Cell Header to New Values */
  477. G_adjust_Cell_head(&cellhd, 1, 1);
  478. if (cellhd.proj == PROJECTION_LL && cellhd.ew_res / cellhd.ns_res > 10.)
  479. /* TODO: find a reasonable value */
  480. G_warning(
  481. _("East-West (ewres: %f) and North-South (nwres: %f) "
  482. "resolution differ significantly. "
  483. "Did you assign %s= and %s= correctly?"),
  484. cellhd.ew_res, cellhd.ns_res, parm.east->key, parm.west->key);
  485. grass_nrows = nrows = cellhd.rows;
  486. grass_ncols = ncols = cellhd.cols;
  487. Rast_set_window(&cellhd);
  488. if (grass_nrows != Rast_window_rows())
  489. G_fatal_error("rows changed from %d to %d",
  490. grass_nrows, Rast_window_rows());
  491. if (grass_ncols != Rast_window_cols())
  492. G_fatal_error("cols changed from %d to %d",
  493. grass_ncols, Rast_window_cols());
  494. expected = (off_t) ncols * nrows * bytes * nbands + hbytes;
  495. if (file_size != expected) {
  496. G_warning(_("File Size %"PRI_OFF_T" ... Total Bytes %"PRI_OFF_T),
  497. file_size, expected);
  498. G_fatal_error(_("Bytes do not match file size"));
  499. }
  500. in_buf = G_malloc(ncols * bytes);
  501. out_buf = Rast_allocate_d_buf();
  502. map_type = is_fp ? (bytes > 4 ? DCELL_TYPE : FCELL_TYPE) : CELL_TYPE;
  503. in_buf = G_malloc(ncols * bytes);
  504. out_buf = Rast_allocate_d_buf();
  505. bsize = log10(nbands) + 1;
  506. if (!flag.gmt_hd->answer && hbytes > 0)
  507. G_fseek(fp, hbytes, SEEK_SET);
  508. for (band = 1; band <= nbands; band++) {
  509. if (nbands > 1) {
  510. G_message(_("Importing band %d..."), band);
  511. sprintf(output, "%s%0*d", outpre, bsize, band);
  512. }
  513. else
  514. sprintf(output, "%s", outpre);
  515. fd = Rast_open_new(output, map_type);
  516. band_off = (off_t)nrows * ncols * bytes * (band - 1) + hbytes;
  517. for (row = 0; row < grass_nrows; row++) {
  518. G_percent(row, nrows, 2);
  519. if (flip & FLIP_V) {
  520. G_fseek(fp, (off_t) (grass_nrows - row - 1) * ncols * bytes + band_off,
  521. SEEK_SET);
  522. }
  523. if (fread(in_buf, bytes, ncols, fp) != ncols)
  524. G_fatal_error(_("Error reading data"));
  525. convert_row(out_buf, in_buf, ncols, is_fp, is_signed,
  526. bytes, swap_flag, null_val, flip);
  527. Rast_put_d_row(fd, out_buf);
  528. }
  529. G_percent(row, nrows, 2); /* finish it off */
  530. Rast_close(fd);
  531. G_debug(1, "Creating support files for %s", output);
  532. if (title)
  533. Rast_put_cell_title(output, title);
  534. Rast_short_history(output, "raster", &history);
  535. Rast_command_history(&history);
  536. Rast_write_history(output, &history);
  537. }
  538. fclose(fp);
  539. return EXIT_SUCCESS;
  540. }