main.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /****************************************************************************
  2. *
  3. * MODULE: r.object.geometry
  4. *
  5. * AUTHOR(S): Moritz Lennert
  6. * Markus Metz
  7. *
  8. * PURPOSE: Fetch object geometry parameters.
  9. *
  10. * COPYRIGHT: (C) 2016-2021 by the GRASS Development Team
  11. *
  12. * This program is free software under the GNU General Public
  13. * License (>=v2). Read the file COPYING that comes with GRASS
  14. * for details.
  15. *
  16. ***************************************************************************/
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <stdio.h>
  20. #include <math.h>
  21. #include <grass/gis.h>
  22. #include <grass/raster.h>
  23. #include <grass/glocale.h>
  24. /* compare two cell values
  25. * return 0 if equal, 1 if different */
  26. static int cmp_cells(CELL a, CELL b, int a_null, int b_null)
  27. {
  28. return (a_null + b_null == 1 || (a_null + b_null == 0 && a != b));
  29. }
  30. int main(int argc, char *argv[])
  31. {
  32. int row, col, nrows, ncols;
  33. struct Range range;
  34. CELL min, max;
  35. int in_fd;
  36. int i;
  37. struct GModule *module;
  38. struct Option *opt_in;
  39. struct Option *opt_out;
  40. struct Option *opt_sep;
  41. struct Flag *flag_m;
  42. char *sep;
  43. FILE *out_fp;
  44. CELL *prev_in, *cur_in, *temp_in;
  45. CELL cur, top, left;
  46. int cur_null, top_null, left_null;
  47. int len;
  48. struct obj_geo
  49. {
  50. double area, perimeter, x, y;
  51. int min_row, max_row, min_col, max_col; /* bounding box */
  52. int num;
  53. } *obj_geos;
  54. double unit_area;
  55. int n_objects;
  56. int planimetric, compute_areas;
  57. struct Cell_head cellhd;
  58. G_gisinit(argv[0]);
  59. /* Define the different options */
  60. module = G_define_module();
  61. G_add_keyword(_("raster"));
  62. G_add_keyword(_("statistics"));
  63. G_add_keyword(_("reclass"));
  64. G_add_keyword(_("clumps"));
  65. module->description =
  66. _("Calculates geometry parameters for raster objects.");
  67. opt_in = G_define_standard_option(G_OPT_R_INPUT);
  68. opt_out = G_define_standard_option(G_OPT_F_OUTPUT);
  69. opt_out->required = NO;
  70. opt_sep = G_define_standard_option(G_OPT_F_SEP);
  71. flag_m = G_define_flag();
  72. flag_m->key = 'm';
  73. flag_m->label = _("Use meters as units instead of cells");
  74. /* parse options */
  75. if (G_parser(argc, argv))
  76. exit(EXIT_FAILURE);
  77. sep = G_option_to_separator(opt_sep);
  78. in_fd = Rast_open_old(opt_in->answer, "");
  79. if (Rast_get_map_type(in_fd) != CELL_TYPE)
  80. G_fatal_error(_("Input raster must be of type CELL"));
  81. if (opt_out->answer != NULL && strcmp(opt_out->answer, "-") != 0) {
  82. if (!(out_fp = fopen(opt_out->answer, "w")))
  83. G_fatal_error(_("Unable to open file <%s> for writing"),
  84. opt_out->answer);
  85. }
  86. else {
  87. out_fp = stdout;
  88. }
  89. Rast_get_window(&cellhd);
  90. nrows = cellhd.rows;
  91. ncols = cellhd.cols;
  92. /* allocate CELL buffers two columns larger than current window */
  93. len = (ncols + 2) * sizeof(CELL);
  94. prev_in = (CELL *) G_malloc(len);
  95. cur_in = (CELL *) G_malloc(len);
  96. /* fake a previous row which is all NULL */
  97. Rast_set_c_null_value(prev_in, ncols + 2);
  98. /* set left and right edge to NULL */
  99. Rast_set_c_null_value(&cur_in[0], 1);
  100. Rast_set_c_null_value(&cur_in[ncols + 1], 1);
  101. Rast_read_range(opt_in->answer, "", &range);
  102. Rast_get_range_min_max(&range, &min, &max);
  103. if (Rast_is_c_null_value(&min) || Rast_is_c_null_value(&max))
  104. G_fatal_error(_("Empty input map <%s>"), opt_in->answer);
  105. /* REMARK: The following is only true if object ids are continuously numbered */
  106. n_objects = max - min + 1;
  107. obj_geos = G_malloc(n_objects * sizeof(struct obj_geo));
  108. for (i = 0; i < n_objects; i++) {
  109. obj_geos[i].area = 0;
  110. obj_geos[i].perimeter = 0;
  111. obj_geos[i].min_row = nrows;
  112. obj_geos[i].max_row = 0;
  113. obj_geos[i].min_col = ncols;
  114. obj_geos[i].max_col = 0;
  115. obj_geos[i].x = 0;
  116. obj_geos[i].y = 0;
  117. obj_geos[i].num = 0;
  118. }
  119. unit_area = 0.0;
  120. if (flag_m->answer) {
  121. switch (G_begin_cell_area_calculations()) {
  122. case 0: /* areas don't make sense, but ignore this for now */
  123. case 1:
  124. planimetric = 1;
  125. unit_area = G_area_of_cell_at_row(0);
  126. break;
  127. default:
  128. planimetric = 0;
  129. break;
  130. }
  131. }
  132. compute_areas = flag_m->answer && !planimetric;
  133. G_begin_distance_calculations();
  134. G_message(_("Calculating statistics"));
  135. for (row = 0; row < nrows; row++) {
  136. G_percent(row, nrows + 1, 2);
  137. Rast_get_c_row(in_fd, cur_in + 1, row);
  138. cur = cur_in[0];
  139. cur_null = 1;
  140. if (compute_areas)
  141. unit_area = G_area_of_cell_at_row(row);
  142. for (col = 1; col <= ncols; col++) {
  143. left = cur;
  144. cur = cur_in[col];
  145. top = prev_in[col];
  146. left_null = cur_null;
  147. cur_null = Rast_is_c_null_value(&cur);
  148. top_null = Rast_is_c_null_value(&top);
  149. if (!cur_null) {
  150. if (flag_m->answer) {
  151. obj_geos[cur - min].area += unit_area;
  152. obj_geos[cur - min].num += 1;
  153. }
  154. else {
  155. obj_geos[cur - min].area += 1;
  156. }
  157. obj_geos[cur - min].x +=
  158. Rast_col_to_easting(col - 0.5, &cellhd);
  159. obj_geos[cur - min].y +=
  160. Rast_row_to_northing(row + 0.5, &cellhd);
  161. if (obj_geos[cur - min].min_row > row)
  162. obj_geos[cur - min].min_row = row;
  163. if (obj_geos[cur - min].max_row < row + 1)
  164. obj_geos[cur - min].max_row = row + 1;
  165. if (obj_geos[cur - min].min_col > col)
  166. obj_geos[cur - min].min_col = col;
  167. if (obj_geos[cur - min].max_col < col + 1)
  168. obj_geos[cur - min].max_col = col + 1;
  169. }
  170. if (cmp_cells(cur, top, cur_null, top_null)) {
  171. if (flag_m->answer) {
  172. double perimeter;
  173. /* could be optimized */
  174. perimeter = G_distance(cellhd.west + col * cellhd.ew_res,
  175. Rast_row_to_northing(row, &cellhd),
  176. cellhd.west + (col +
  177. 1) * cellhd.ew_res,
  178. Rast_row_to_northing(row,
  179. &cellhd));
  180. if (!cur_null)
  181. obj_geos[cur - min].perimeter += perimeter;
  182. if (!top_null)
  183. obj_geos[top - min].perimeter += perimeter;
  184. }
  185. else {
  186. if (!cur_null)
  187. obj_geos[cur - min].perimeter += 1;
  188. if (!top_null)
  189. obj_geos[top - min].perimeter += 1;
  190. }
  191. }
  192. if (cmp_cells(cur, left, cur_null, left_null)) {
  193. if (flag_m->answer) {
  194. double perimeter;
  195. /* could be optimized */
  196. perimeter = G_distance(cellhd.west + col * cellhd.ew_res,
  197. Rast_row_to_northing(row, &cellhd),
  198. cellhd.west +
  199. (col) * cellhd.ew_res,
  200. Rast_row_to_northing(row + 1,
  201. &cellhd));
  202. if (!cur_null)
  203. obj_geos[cur - min].perimeter += perimeter;
  204. if (!left_null)
  205. obj_geos[left - min].perimeter += perimeter;
  206. }
  207. else {
  208. if (!cur_null)
  209. obj_geos[cur - min].perimeter += 1;
  210. if (!left_null)
  211. obj_geos[left - min].perimeter += 1;
  212. }
  213. }
  214. }
  215. /* last col, right borders */
  216. if (flag_m->answer) {
  217. double perimeter;
  218. perimeter = G_distance(cellhd.east,
  219. Rast_row_to_northing(row, &cellhd),
  220. cellhd.east,
  221. Rast_row_to_northing(row + 1, &cellhd));
  222. if (!cur_null)
  223. obj_geos[cur - min].perimeter += perimeter;
  224. }
  225. else {
  226. if (!cur_null)
  227. obj_geos[cur - min].perimeter += 1;
  228. }
  229. /* switch the buffers so that the current buffer becomes the previous */
  230. temp_in = cur_in;
  231. cur_in = prev_in;
  232. prev_in = temp_in;
  233. }
  234. /* last row, bottom borders */
  235. G_percent(row, nrows + 1, 2);
  236. for (col = 1; col <= ncols; col++) {
  237. top = prev_in[col];
  238. top_null = Rast_is_c_null_value(&top);
  239. if (flag_m->answer) {
  240. double perimeter;
  241. /* could be optimized */
  242. perimeter = G_distance(cellhd.west + col * cellhd.ew_res,
  243. Rast_row_to_northing(row, &cellhd),
  244. cellhd.west + (col + 1) * cellhd.ew_res,
  245. Rast_row_to_northing(row, &cellhd));
  246. if (!top_null)
  247. obj_geos[top - min].perimeter += perimeter;
  248. }
  249. else {
  250. if (!top_null)
  251. obj_geos[top - min].perimeter += 1;
  252. }
  253. }
  254. G_percent(1, 1, 1);
  255. Rast_close(in_fd);
  256. G_free(cur_in);
  257. G_free(prev_in);
  258. G_message(_("Writing output"));
  259. /* print table */
  260. fprintf(out_fp, "cat%s", sep);
  261. fprintf(out_fp, "area%s", sep);
  262. fprintf(out_fp, "perimeter%s", sep);
  263. fprintf(out_fp, "compact_square%s", sep);
  264. fprintf(out_fp, "compact_circle%s", sep);
  265. fprintf(out_fp, "fd%s", sep);
  266. fprintf(out_fp, "mean_x%s", sep);
  267. fprintf(out_fp, "mean_y");
  268. fprintf(out_fp, "\n");
  269. /* print table body */
  270. for (i = 0; i < n_objects; i++) {
  271. G_percent(i, n_objects - 1, 1);
  272. /* skip empty objects */
  273. if (obj_geos[i].area == 0)
  274. continue;
  275. fprintf(out_fp, "%d%s", min + i, sep);
  276. fprintf(out_fp, "%f%s", obj_geos[i].area, sep);
  277. fprintf(out_fp, "%f%s", obj_geos[i].perimeter, sep);
  278. fprintf(out_fp, "%f%s",
  279. 4 * sqrt(obj_geos[i].area) / obj_geos[i].perimeter, sep);
  280. fprintf(out_fp, "%f%s",
  281. obj_geos[i].perimeter / (2 * sqrt(M_PI * obj_geos[i].area)),
  282. sep);
  283. /* log 1 = 0, so avoid that by always adding 0.001 to the area: */
  284. fprintf(out_fp, "%f%s",
  285. 2 * log(obj_geos[i].perimeter) / log(obj_geos[i].area +
  286. 0.001), sep);
  287. if (!flag_m->answer)
  288. obj_geos[i].num = obj_geos[i].area;
  289. fprintf(out_fp, "%f%s", obj_geos[i].x / obj_geos[i].num, sep);
  290. fprintf(out_fp, "%f", obj_geos[i].y / obj_geos[i].num);
  291. /* object id: i + min */
  292. /* TODO */
  293. /* smoothness */
  294. /* perimeter of bounding box / perimeter -> smoother objects have a higher smoothness value
  295. * smoothness is in the range 0 < smoothness <= 1 */
  296. /* bounding box perimeter */
  297. /* bounding box size */
  298. /* variance of X and Y to approximate bounding ellipsoid */
  299. fprintf(out_fp, "\n");
  300. }
  301. if (out_fp != stdout)
  302. fclose(out_fp);
  303. exit(EXIT_SUCCESS);
  304. }