|
@@ -0,0 +1,354 @@
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ *
|
|
|
+ * 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 mus 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);
|
|
|
+}
|