123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- /****************************************************************************
- *
- * MODULE: r.object.geometry
- *
- * AUTHOR(S): Moritz Lennert
- * Markus Metz
- *
- * PURPOSE: Fetch object geometry parameters.
- *
- * COPYRIGHT: (C) 2016-2021 by the GRASS Development Team
- *
- * This program is free software under the GNU General Public
- * License (>=v2). Read the file COPYING that comes with GRASS
- * for details.
- *
- ***************************************************************************/
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>
- #include <math.h>
- #include <grass/gis.h>
- #include <grass/raster.h>
- #include <grass/glocale.h>
- /* compare two cell values
- * return 0 if equal, 1 if different */
- static int cmp_cells(CELL a, CELL b, int a_null, int b_null)
- {
- return (a_null + b_null == 1 || (a_null + b_null == 0 && a != b));
- }
- int main(int argc, char *argv[])
- {
- int row, col, nrows, ncols;
- struct Range range;
- CELL min, max;
- int in_fd;
- int i;
- struct GModule *module;
- struct Option *opt_in;
- struct Option *opt_out;
- struct Option *opt_sep;
- struct Flag *flag_m;
- char *sep;
- FILE *out_fp;
- CELL *prev_in, *cur_in, *temp_in;
- CELL cur, top, left;
- int cur_null, top_null, left_null;
- int len;
- struct obj_geo
- {
- double area, perimeter, x, y;
- int min_row, max_row, min_col, max_col; /* bounding box */
- int num;
- } *obj_geos;
- double unit_area;
- int n_objects;
- int planimetric, compute_areas;
- struct Cell_head cellhd;
- G_gisinit(argv[0]);
- /* Define the different options */
- module = G_define_module();
- G_add_keyword(_("raster"));
- G_add_keyword(_("statistics"));
- G_add_keyword(_("reclass"));
- G_add_keyword(_("clumps"));
- module->description =
- _("Calculates geometry parameters for raster objects.");
- opt_in = G_define_standard_option(G_OPT_R_INPUT);
- opt_out = G_define_standard_option(G_OPT_F_OUTPUT);
- opt_out->required = NO;
- opt_sep = G_define_standard_option(G_OPT_F_SEP);
- flag_m = G_define_flag();
- flag_m->key = 'm';
- flag_m->label = _("Use meters as units instead of cells");
- /* parse options */
- if (G_parser(argc, argv))
- exit(EXIT_FAILURE);
- sep = G_option_to_separator(opt_sep);
- in_fd = Rast_open_old(opt_in->answer, "");
- if (Rast_get_map_type(in_fd) != CELL_TYPE)
- G_fatal_error(_("Input raster must be of type CELL"));
- if (opt_out->answer != NULL && strcmp(opt_out->answer, "-") != 0) {
- if (!(out_fp = fopen(opt_out->answer, "w")))
- G_fatal_error(_("Unable to open file <%s> for writing"),
- opt_out->answer);
- }
- else {
- out_fp = stdout;
- }
- Rast_get_window(&cellhd);
- nrows = cellhd.rows;
- ncols = cellhd.cols;
- /* allocate CELL buffers two columns larger than current window */
- len = (ncols + 2) * sizeof(CELL);
- prev_in = (CELL *) G_malloc(len);
- cur_in = (CELL *) G_malloc(len);
- /* fake a previous row which is all NULL */
- Rast_set_c_null_value(prev_in, ncols + 2);
- /* set left and right edge to NULL */
- Rast_set_c_null_value(&cur_in[0], 1);
- Rast_set_c_null_value(&cur_in[ncols + 1], 1);
- Rast_read_range(opt_in->answer, "", &range);
- Rast_get_range_min_max(&range, &min, &max);
- if (Rast_is_c_null_value(&min) || Rast_is_c_null_value(&max))
- G_fatal_error(_("Empty input map <%s>"), opt_in->answer);
- /* REMARK: The following is only true if object ids are continuously numbered */
- n_objects = max - min + 1;
- obj_geos = G_malloc(n_objects * sizeof(struct obj_geo));
- for (i = 0; i < n_objects; i++) {
- obj_geos[i].area = 0;
- obj_geos[i].perimeter = 0;
- obj_geos[i].min_row = nrows;
- obj_geos[i].max_row = 0;
- obj_geos[i].min_col = ncols;
- obj_geos[i].max_col = 0;
- obj_geos[i].x = 0;
- obj_geos[i].y = 0;
- obj_geos[i].num = 0;
- }
- unit_area = 0.0;
- if (flag_m->answer) {
- switch (G_begin_cell_area_calculations()) {
- case 0: /* areas don't make sense, but ignore this for now */
- case 1:
- planimetric = 1;
- unit_area = G_area_of_cell_at_row(0);
- break;
- default:
- planimetric = 0;
- break;
- }
- }
- compute_areas = flag_m->answer && !planimetric;
- G_begin_distance_calculations();
- G_message(_("Calculating statistics"));
- for (row = 0; row < nrows; row++) {
- G_percent(row, nrows + 1, 2);
- Rast_get_c_row(in_fd, cur_in + 1, row);
- cur = cur_in[0];
- cur_null = 1;
- if (compute_areas)
- unit_area = G_area_of_cell_at_row(row);
- for (col = 1; col <= ncols; col++) {
- left = cur;
- cur = cur_in[col];
- top = prev_in[col];
- left_null = cur_null;
- cur_null = Rast_is_c_null_value(&cur);
- top_null = Rast_is_c_null_value(&top);
- if (!cur_null) {
- if (flag_m->answer) {
- obj_geos[cur - min].area += unit_area;
- obj_geos[cur - min].num += 1;
- }
- else {
- obj_geos[cur - min].area += 1;
- }
- obj_geos[cur - min].x +=
- Rast_col_to_easting(col - 0.5, &cellhd);
- obj_geos[cur - min].y +=
- Rast_row_to_northing(row + 0.5, &cellhd);
- if (obj_geos[cur - min].min_row > row)
- obj_geos[cur - min].min_row = row;
- if (obj_geos[cur - min].max_row < row + 1)
- obj_geos[cur - min].max_row = row + 1;
- if (obj_geos[cur - min].min_col > col)
- obj_geos[cur - min].min_col = col;
- if (obj_geos[cur - min].max_col < col + 1)
- obj_geos[cur - min].max_col = col + 1;
- }
- if (cmp_cells(cur, top, cur_null, top_null)) {
- if (flag_m->answer) {
- double perimeter;
- /* could be optimized */
- perimeter = G_distance(cellhd.west + col * cellhd.ew_res,
- Rast_row_to_northing(row, &cellhd),
- cellhd.west + (col +
- 1) * cellhd.ew_res,
- Rast_row_to_northing(row,
- &cellhd));
- if (!cur_null)
- obj_geos[cur - min].perimeter += perimeter;
- if (!top_null)
- obj_geos[top - min].perimeter += perimeter;
- }
- else {
- if (!cur_null)
- obj_geos[cur - min].perimeter += 1;
- if (!top_null)
- obj_geos[top - min].perimeter += 1;
- }
- }
- if (cmp_cells(cur, left, cur_null, left_null)) {
- if (flag_m->answer) {
- double perimeter;
- /* could be optimized */
- perimeter = G_distance(cellhd.west + col * cellhd.ew_res,
- Rast_row_to_northing(row, &cellhd),
- cellhd.west +
- (col) * cellhd.ew_res,
- Rast_row_to_northing(row + 1,
- &cellhd));
- if (!cur_null)
- obj_geos[cur - min].perimeter += perimeter;
- if (!left_null)
- obj_geos[left - min].perimeter += perimeter;
- }
- else {
- if (!cur_null)
- obj_geos[cur - min].perimeter += 1;
- if (!left_null)
- obj_geos[left - min].perimeter += 1;
- }
- }
- }
- /* last col, right borders */
- if (flag_m->answer) {
- double perimeter;
- perimeter = G_distance(cellhd.east,
- Rast_row_to_northing(row, &cellhd),
- cellhd.east,
- Rast_row_to_northing(row + 1, &cellhd));
- if (!cur_null)
- obj_geos[cur - min].perimeter += perimeter;
- }
- else {
- if (!cur_null)
- obj_geos[cur - min].perimeter += 1;
- }
- /* switch the buffers so that the current buffer becomes the previous */
- temp_in = cur_in;
- cur_in = prev_in;
- prev_in = temp_in;
- }
- /* last row, bottom borders */
- G_percent(row, nrows + 1, 2);
- for (col = 1; col <= ncols; col++) {
- top = prev_in[col];
- top_null = Rast_is_c_null_value(&top);
- if (flag_m->answer) {
- double perimeter;
- /* could be optimized */
- perimeter = G_distance(cellhd.west + col * cellhd.ew_res,
- Rast_row_to_northing(row, &cellhd),
- cellhd.west + (col + 1) * cellhd.ew_res,
- Rast_row_to_northing(row, &cellhd));
- if (!top_null)
- obj_geos[top - min].perimeter += perimeter;
- }
- else {
- if (!top_null)
- obj_geos[top - min].perimeter += 1;
- }
- }
- G_percent(1, 1, 1);
- Rast_close(in_fd);
- G_free(cur_in);
- G_free(prev_in);
- G_message(_("Writing output"));
- /* print table */
- fprintf(out_fp, "cat%s", sep);
- fprintf(out_fp, "area%s", sep);
- fprintf(out_fp, "perimeter%s", sep);
- fprintf(out_fp, "compact_square%s", sep);
- fprintf(out_fp, "compact_circle%s", sep);
- fprintf(out_fp, "fd%s", sep);
- fprintf(out_fp, "mean_x%s", sep);
- fprintf(out_fp, "mean_y");
- fprintf(out_fp, "\n");
- /* print table body */
- for (i = 0; i < n_objects; i++) {
- G_percent(i, n_objects - 1, 1);
- /* skip empty objects */
- if (obj_geos[i].area == 0)
- continue;
- fprintf(out_fp, "%d%s", min + i, sep);
- fprintf(out_fp, "%f%s", obj_geos[i].area, sep);
- fprintf(out_fp, "%f%s", obj_geos[i].perimeter, sep);
- fprintf(out_fp, "%f%s",
- 4 * sqrt(obj_geos[i].area) / obj_geos[i].perimeter, sep);
- fprintf(out_fp, "%f%s",
- obj_geos[i].perimeter / (2 * sqrt(M_PI * obj_geos[i].area)),
- sep);
- /* log 1 = 0, so avoid that by always adding 0.001 to the area: */
- fprintf(out_fp, "%f%s",
- 2 * log(obj_geos[i].perimeter) / log(obj_geos[i].area +
- 0.001), sep);
- if (!flag_m->answer)
- obj_geos[i].num = obj_geos[i].area;
- fprintf(out_fp, "%f%s", obj_geos[i].x / obj_geos[i].num, sep);
- fprintf(out_fp, "%f", obj_geos[i].y / obj_geos[i].num);
- /* object id: i + min */
- /* TODO */
- /* smoothness */
- /* perimeter of bounding box / perimeter -> smoother objects have a higher smoothness value
- * smoothness is in the range 0 < smoothness <= 1 */
- /* bounding box perimeter */
- /* bounding box size */
- /* variance of X and Y to approximate bounding ellipsoid */
- fprintf(out_fp, "\n");
- }
- if (out_fp != stdout)
- fclose(out_fp);
- exit(EXIT_SUCCESS);
- }
|