Skip to content

Commit

Permalink
r.mask.status: Check mask status through a tool and function (#2390)
Browse files Browse the repository at this point in the history
Instead of using low-level test of file existence with hardcoded raster path and name, this offers a new tool to retrieve status of the raster mask.

The new r.mask.status tool reports presence or absence of the 2D raster mask and provides additional details about the mask.

There is one usage of this now and that's the one for a shell prompt. The prompt no longer relies on testing the file presence with the test program, but uses a GRASS tool to find out.

The code goes out of its way to report both mask name (currently always MASK) and the underlying raster name if it is a reclassified (without rewriting the current C API). This is to mimic the existing C functions which are returning the underlying raster if MASK is a reclass. The tool and the new C API function return both preparing a way for using an arbitrary name for the mask while having the option to look at the underlying reclassified raster map.
  • Loading branch information
wenzeslaus authored Oct 11, 2024
1 parent cbc3ff4 commit 1d42e58
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 27 deletions.
1 change: 1 addition & 0 deletions include/grass/defs/raster.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ int Rast_option_to_interp_type(const struct Option *);

/* mask_info.c */
char *Rast_mask_info(void);
bool Rast_mask_status(char *, char *, bool *, char *, char *);
int Rast__mask_info(char *, char *);
bool Rast_mask_is_present(void);

Expand Down
2 changes: 1 addition & 1 deletion lib/init/grass.py
Original file line number Diff line number Diff line change
Expand Up @@ -1667,8 +1667,8 @@ def sh_like_startup(location, location_name, grass_env_file, sh):
)
)

mask2d_test = "r.mask.status -t"
# TODO: have a function and/or module to test this
mask2d_test = 'test -f "$MAPSET_PATH/cell/MASK"'
mask3d_test = 'test -d "$MAPSET_PATH/grid3/RASTER3D_MASK"'

specific_addition = ""
Expand Down
120 changes: 94 additions & 26 deletions lib/raster/mask_info.c
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
/*
*************************************************************
* char * Rast_mask_info ()
*
* returns a printable text of mask information
*
************************************************************
* Rast__mask_info (name, mapset)
*
* char name[GNAME_MAX], mapset[GMAPSET_MAX];
*
* function:
* determine the status off the automatic masking
* and the name of the cell file which forms the mask
/**
* \file lib/raster/mask_info.c
*
* (the mask file is actually MASK in the current mapset,
* but is usually a reclassed cell file, and the reclass
* name and mapset are returned)
* \brief Raster Library - Get mask information
*
* returns:
* -1 no masking (name, mapset undefined)
* name, mapset are undefined
* (C) 1999-2024 by Vaclav Petras and the GRASS Development Team
*
* 1 mask file present, masking on
* name, mapset hold mask file name, mapset
* This program is free software under the GNU General Public
* License (>=v2). Read the file COPYING that comes with GRASS
* for details.
*
***************************************************************/
* \author CERL
* \author Vaclav Petras, NC State University, Center for Geospatial Analytics
*/

#include <string.h>

#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/glocale.h>

/**
* @brief Get a printable text with information about raster mask
*
* Determines if 2D raster mask is present and returns textual information about
* the mask suitable for end-user display. The resulting text is translated.
* Caller is responsible for freeing the memory of the returned string.
*
* @return New string with textual information
*/
char *Rast_mask_info(void)
{
char text[GNAME_MAX + GMAPSET_MAX + 16];
Expand All @@ -53,16 +49,88 @@ char *Rast_mask_info(void)
return G_store(text);
}

/**
* @brief Get raster mask status information
*
* _is_mask_reclass_ is a pointer to a bool variable which
* will be set to true if mask raster is a reclass and false otherwise.
*
* If you are not interested in the underlying reclassified raster map,
* pass NULL pointers for the three reclass parameters:
*
* ```
* Rast_mask_status(name, mapset, NULL, NULL, NULL);
* ```
*
* @param[out] name Name of the raster map used as mask
* @param[out] mapset Name of the mapset the raster is in
* @param[out] is_mask_reclass Will be set to true if mask raster is a reclass
* @param[out] reclass_name Name of the underlying reclassified raster map
* @param[out] reclass_mapset Name of the mapset the reclassified raster is in
*
* @return true if mask is present, false otherwise
*/
bool Rast_mask_status(char *name, char *mapset, bool *is_mask_reclass,
char *reclass_name, char *reclass_mapset)
{
int present = Rast__mask_info(name, mapset);

if (is_mask_reclass && reclass_name && reclass_mapset) {
if (present) {
*is_mask_reclass = Rast_is_reclass("MASK", G_mapset(), reclass_name,
reclass_mapset) > 0;
if (*is_mask_reclass) {
// The original mask values were overwritten in the initial
// info call. Put back the original values, so that we can
// report them to the caller.
strcpy(name, "MASK");
strcpy(mapset, G_mapset());
}
}
else {
*is_mask_reclass = false;
}
}

if (present == 1)
return true;
else
return false;
}

/**
* @brief Get information about the current mask
*
* Determines the status of the automatic masking and the name of the 2D
* raster which forms the mask. Typically, mask is raster called MASK in the
* current mapset, but when used with r.mask, it is usually a reclassed
* raster, and so when a MASK raster is present and it is a reclass raster,
* the name and mapset of the underlying reclassed raster are returned.
*
* The name and mapset is written to the parameter which need to be defined
* with a sufficient size, least as `char name[GNAME_MAX], mapset[GMAPSET_MAX]`.
*
* When the masking is not active, -1 is returned and name and mapset are
* undefined. When the masking is active, 1 is returned and name and mapset
* will hold the name and mapset of the underlying raster.
*
* @param[out] name Name of the raster map used as mask
* @param[out] mapset Name of the map's mapset
*
* @return 1 if mask is present, -1 otherwise
*/
int Rast__mask_info(char *name, char *mapset)
{
char rname[GNAME_MAX], rmapset[GMAPSET_MAX];

strcpy(name, "MASK");
strcpy(mapset, G_mapset());
strcpy(rname, "MASK");
strcpy(rmapset, G_mapset());

if (!G_find_raster(name, mapset))
if (!G_find_raster(rname, rmapset))
return -1;

strcpy(name, rname);
strcpy(mapset, rmapset);
if (Rast_is_reclass(name, mapset, rname, rmapset) > 0) {
strcpy(name, rname);
strcpy(mapset, rmapset);
Expand Down
1 change: 1 addition & 0 deletions raster/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ SUBDIRS = \
r.lake \
r.li \
r.mapcalc \
r.mask.status \
r.mfilter \
r.mode \
r.neighbors \
Expand Down
10 changes: 10 additions & 0 deletions raster/r.mask.status/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
MODULE_TOPDIR = ../..

PGM = r.mask.status

LIBES = $(MANAGELIB) $(RASTERLIB) $(GISLIB) $(PARSONLIB)
DEPENDENCIES = $(MANAGEDEP) $(RASTERDEP) $(GISDEP)

include $(MODULE_TOPDIR)/include/Make/Module.make

default: cmd
184 changes: 184 additions & 0 deletions raster/r.mask.status/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/****************************************************************************
*
* MODULE: r.mask.status
* AUTHORS: Vaclav Petras
* PURPOSE: Report status of raster mask
* COPYRIGHT: (C) 2024 by Vaclav Petras and 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <grass/gis.h>
#include <grass/parson.h>
#include <grass/raster.h>
#include <grass/glocale.h>

struct Parameters {
struct Option *format;
struct Flag *like_test;
};

void parse_parameters(struct Parameters *params, int argc, char **argv)
{
struct GModule *module;

module = G_define_module();
G_add_keyword(_("raster"));
G_add_keyword(_("mask"));
G_add_keyword(_("reclassification"));
module->label = _("Reports presence or absence of a raster mask");
module->description =
_("Provides information about the presence of a 2D raster mask"
" as text output or return code");

params->format = G_define_option();
params->format->key = "format";
params->format->type = TYPE_STRING;
params->format->required = NO;
params->format->answer = "plain";
params->format->options = "plain,json,shell,yaml";
params->format->descriptions =
"plain;Plain text output;"
"json;JSON (JavaScript Object Notation);"
"shell;Shell script style output;"
"yaml;YAML (human-friendly data serialization language)";
params->format->description = _("Format for reporting");

params->like_test = G_define_flag();
params->like_test->key = 't';
params->like_test->label =
_("Return code 0 when mask present, 1 otherwise");
params->like_test->description =
_("Behave like the test utility, 0 for true, 1 for false, no output");
// suppress_required is not required given the default value for format.
// Both no parameters and only -t work as expected.

if (G_parser(argc, argv))
exit(EXIT_FAILURE);
}

int report_status(struct Parameters *params)
{

char name[GNAME_MAX];
char mapset[GMAPSET_MAX];
char reclass_name[GNAME_MAX];
char reclass_mapset[GMAPSET_MAX];

bool is_mask_reclass;
bool present = Rast_mask_status(name, mapset, &is_mask_reclass,
reclass_name, reclass_mapset);

// This does not have to be exclusive with the printing, but leaving this
// to a different boolean flag which could do the return code and printing.
// The current implementation really behaves like the test utility which
// facilitates the primary usage of this which is prompt building
// (and there any output would be noise).
if (params->like_test->answer) {
if (present)
return 0;
return 1;
}

// Mask raster
char *full_mask = G_fully_qualified_name(name, mapset);
// Underlying raster if applicable
char *full_underlying = NULL;
if (is_mask_reclass)
full_underlying = G_fully_qualified_name(reclass_name, reclass_mapset);

if (strcmp(params->format->answer, "json") == 0) {
JSON_Value *root_value = json_value_init_object();
JSON_Object *root_object = json_object(root_value);
json_object_set_boolean(root_object, "present", present);
if (present)
json_object_set_string(root_object, "full_name", full_mask);
else
json_object_set_null(root_object, "full_name");
if (is_mask_reclass)
json_object_set_string(root_object, "is_reclass_of",
full_underlying);
else
json_object_set_null(root_object, "is_reclass_of");
char *serialized_string = json_serialize_to_string_pretty(root_value);
puts(serialized_string);
json_free_serialized_string(serialized_string);
json_value_free(root_value);
}
else if (strcmp(params->format->answer, "shell") == 0) {
printf("present=");
if (present)
printf("1");
else
printf("0");
printf("\nfull_name=");
if (present)
printf("%s", full_mask);
printf("\nis_reclass_of=");
if (is_mask_reclass)
printf("%s", full_underlying);
printf("\n");
}
else if (strcmp(params->format->answer, "yaml") == 0) {
printf("present: ");
if (present)
printf("true");
else
printf("false");
printf("\nfull_name: ");
if (present)
printf("|-\n %s", full_mask);
else
printf("null");
// Null values in YAML can be an empty (no) value (rather than null),
// so we could use that, but using the explicit null as a reasonable
// starting point.
printf("\nis_reclass_of: ");
// Using block scalar with |- to avoid need for escaping.
// Alternatively, we could check mapset naming limits against YAML
// escaping needs for different types of strings and do the necessary
// escaping here.
if (is_mask_reclass)
printf("|-\n %s", full_underlying);
else
printf("null");
printf("\n");
}
else {
if (present)
printf(_("Mask is active"));
else
printf(_("Mask is not present"));
if (present) {
printf("\n");
printf(_("Mask name: %s"), full_mask);
}
if (is_mask_reclass) {
printf("\n");
printf(_("Mask is a raster reclassified from: %s"),
full_underlying);
}
printf("\n");
}

G_free(full_mask);
G_free(full_underlying);
return EXIT_SUCCESS;
}

int main(int argc, char **argv)
{
struct Parameters params;

G_gisinit(argv[0]);
parse_parameters(&params, argc, argv);
return report_status(&params);
}
Loading

0 comments on commit 1d42e58

Please sign in to comment.