GRASS 8 Programmer's Manual  8.5.0dev(2025)-34f31bd7be
reclass.c
Go to the documentation of this file.
1 /*!
2  * \file lib/raster/reclass.c
3  *
4  * \brief Raster Library - Check if raster map is reclassified
5  *
6  * (C) 2001-2009 by the GRASS Development Team
7  *
8  * This program is free software under the GNU General Public License
9  * (>=v2). Read the file COPYING that comes with GRASS for details.
10  *
11  * \author Original author CERL
12  */
13 
14 #include <string.h>
15 #include <stdbool.h>
16 
17 #include <grass/gis.h>
18 #include <grass/raster.h>
19 #include <grass/glocale.h>
20 
21 static const char NULL_STRING[] = "null";
22 static int reclass_type(FILE *, char **, char **, char **);
23 static FILE *fopen_cellhd_old(const char *, const char *);
24 static FILE *fopen_cellhd_new(const char *);
25 static int get_reclass_table(FILE *, struct Reclass *, char **);
26 
27 /*!
28  * \brief Check if raster map is reclassified
29  *
30  * This function determines if the raster map <i>name</i> in
31  * <i>mapset</i> is a reclass file. If it is, then the name and mapset
32  * of the referenced raster map are copied into the <i>rname</i> and
33  * <i>rmapset</i> buffers.
34  *
35  * \param name map name
36  * \param mapset mapset name
37  * \param[out] rname name of reference map
38  * \param[out] rmapset mapset where reference map lives
39  *
40  * \returns 1 if it is a reclass file
41  * \return 0 if it is not
42  * \return -1 if there was a problem reading the raster header
43  */
44 int Rast_is_reclass(const char *name, const char *mapset, char *rname,
45  char *rmapset)
46 {
47  FILE *fd;
48  int type;
49 
50  fd = fopen_cellhd_old(name, mapset);
51  if (fd == NULL)
52  return -1;
53 
54  type = reclass_type(fd, &rname, &rmapset, NULL);
55  fclose(fd);
56  if (type < 0)
57  return -1;
58  else
59  return type != 0;
60 }
61 
62 /*!
63  * \brief Get child reclass maps list
64  *
65  * This function generates a child reclass maps list from the
66  * cell_misc/reclassed_to file which stores this list. The
67  * cell_misc/reclassed_to file is written by Rast_put_reclass().
68  * Rast_is_reclassed_to() is used by <tt>g.rename</tt>, <tt>g.remove</tt>
69  * and <tt>r.reclass</tt> to prevent accidentally deleting the parent
70  * map of a reclassed raster map.
71  *
72  * \param name map name
73  * \param mapset mapset name
74  * \param[out] nrmaps number of reference maps
75  * \param[out] rmaps array of names of reference maps
76  *
77  * \return number of reference maps
78  * \return -1 on error
79  */
80 int Rast_is_reclassed_to(const char *name, const char *mapset, int *nrmaps,
81  char ***rmaps)
82 {
83  FILE *fd;
84  int i, j, k, l;
85  char buf2[256], buf3[256];
86 
87  fd = G_fopen_old_misc("cell_misc", "reclassed_to", name, mapset);
88 
89  if (fd == NULL) {
90  return -1;
91  }
92 
93  if (rmaps)
94  *rmaps = NULL;
95  for (i = 0; !feof(fd) && fgets(buf2, 255, fd);) {
96  l = strlen(buf2);
97  for (j = 0, k = 0; j < l; j++) {
98  if (buf2[j] == '#' ||
99  ((buf2[j] == ' ' || buf2[j] == '\t' || buf2[j] == '\n') && k))
100  break;
101  else if (buf2[j] != ' ' && buf2[j] != '\t')
102  buf3[k++] = buf2[j];
103  }
104 
105  if (k) {
106  buf3[k] = 0;
107  i++;
108  if (rmaps) {
109  *rmaps = (char **)G_realloc(*rmaps, i * sizeof(char *));
110  (*rmaps)[i - 1] = (char *)G_malloc(k + 1);
111  strncpy((*rmaps)[i - 1], buf3, k);
112  (*rmaps)[i - 1][k] = 0;
113  }
114  }
115  }
116 
117  if (nrmaps)
118  *nrmaps = i;
119 
120  if (i && rmaps) {
121  i++;
122  *rmaps = (char **)G_realloc(*rmaps, i * sizeof(char *));
123  (*rmaps)[i - 1] = NULL;
124  }
125 
126  fclose(fd);
127 
128  return i;
129 }
130 
131 /*!
132  \brief Get reclass
133 
134  \param name map name
135  \param mapset mapset name
136  \param[out] reclass pointer to Reclass structure
137 
138  \return type code (>=1), 0 if no reclass, -1 on error
139  */
140 int Rast_get_reclass(const char *name, const char *mapset,
141  struct Reclass *reclass)
142 {
143  FILE *fd;
144  int stat;
145 
146  fd = fopen_cellhd_old(name, mapset);
147  if (fd == NULL)
148  return -1;
149  reclass->name = NULL;
150  reclass->mapset = NULL;
151  char *error_message = NULL;
152  reclass->type =
153  reclass_type(fd, &reclass->name, &reclass->mapset, &error_message);
154  if (reclass->type == 0) {
155  // no reclass
156  fclose(fd);
157  return reclass->type;
158  }
159  if (reclass->type < 0) {
160  // error
161  fclose(fd);
162  G_warning(_("Error reading beginning of header file for <%s@%s>: %s"),
163  name, mapset, error_message);
164  if (error_message != NULL)
165  G_free(error_message);
166  return reclass->type;
167  }
168 
169  switch (reclass->type) {
170  case RECLASS_TABLE:
171  stat = get_reclass_table(fd, reclass, &error_message);
172  break;
173  default:
174  stat = -1;
175  }
176 
177  fclose(fd);
178  if (stat < 0) {
179  if (stat == -2)
180  G_warning(_("Too many reclass categories for <%s@%s>"), name,
181  mapset);
182  else
183  G_warning(
184  _("Illegal reclass format in header file for <%s@%s>: %s"),
185  name, mapset, error_message);
186  stat = -1;
187  }
188  if (error_message != NULL)
189  G_free(error_message);
190  return stat;
191 }
192 
193 /*!
194  \brief Free Reclass structure
195 
196  \param reclass pointer to Reclass structure
197  */
198 void Rast_free_reclass(struct Reclass *reclass)
199 {
200  switch (reclass->type) {
201  case RECLASS_TABLE:
202  if (reclass->num > 0)
203  G_free(reclass->table);
204  reclass->num = 0;
205  if (reclass->name)
206  G_free(reclass->name);
207  if (reclass->mapset)
208  G_free(reclass->mapset);
209  reclass->name = NULL;
210  reclass->mapset = NULL;
211  break;
212  default:
213  break;
214  }
215 }
216 
217 /**
218  * \brief Get reclass type if it is a reclass file
219  *
220  * \param fd[in] file descriptor
221  * \param rname[out] name of the reclass from raster
222  * \param rmapset[out] name of the mapset of the raster
223  * \param error_message[out] will be assigned a newly error message if not NULL
224  *
225  * \returns RECLASS_TABLE if reclass, 0 if not, -1 on error
226  */
227 static int reclass_type(FILE *fd, char **rname, char **rmapset,
228  char **error_message)
229 {
230  char
231  buf[GNAME_MAX + 128 + 1]; // name or mapset plus the label and separator
232  char label[128], arg[GNAME_MAX];
233  int i;
234  int type;
235 
236  /* Check to see if this is a reclass file */
237  if (fgets(buf, sizeof(buf), fd) == NULL)
238  return 0;
239  if (strncmp(buf, "reclas", 6))
240  return 0;
241  /* later may add other types of reclass */
242  type = RECLASS_TABLE;
243 
244  /* Read the mapset and file name of the REAL cell file */
245  if (*rname)
246  **rname = '\0';
247  if (*rmapset)
248  **rmapset = '\0';
249  for (i = 0; i < 2; i++) {
250  if (fgets(buf, sizeof buf, fd) == NULL) {
251  if (error_message != NULL) {
252  G_asprintf(error_message, _("File too short, reading line %d"),
253  i + 1);
254  }
255  return -1;
256  }
257  if (buf[strlen(buf) - 1] != '\n') {
258  if (error_message != NULL) {
259  G_asprintf(error_message, _("Line too long: %s..."), buf);
260  }
261  return -1;
262  }
263  if (sscanf(buf, "%[^:]:%s", label, arg) != 2) {
264  if (error_message != NULL) {
265  G_asprintf(error_message, _("Format is not key:value: %s"),
266  buf);
267  }
268  return -1;
269  }
270  if (strncmp(label, "maps", 4) == 0) {
271  if (*rmapset)
272  strcpy(*rmapset, arg);
273  else
274  *rmapset = G_store(arg);
275  }
276  else if (strncmp(label, "name", 4) == 0) {
277  if (*rname)
278  strcpy(*rname, arg);
279  else
280  *rname = G_store(arg);
281  }
282  else {
283  if (error_message != NULL) {
284  G_asprintf(error_message, _("Unknown key at line: %s"), buf);
285  }
286  return -1;
287  }
288  }
289  if (**rmapset && **rname)
290  return type;
291  else {
292  // If they do not occur in the two lines we expect them.
293  if (**rname && error_message != NULL) {
294  G_asprintf(error_message,
295  _("Mapset not read, only raster name: %s"), *rname);
296  }
297  else if (**rmapset && error_message != NULL) {
298  G_asprintf(error_message,
299  _("Raster name not read, only mapset: %s"), *rmapset);
300  }
301  else if (error_message != NULL) {
302  *error_message = G_store(_("Raster name and mapset not read"));
303  }
304  return -1;
305  }
306 }
307 
308 static FILE *fopen_cellhd_old(const char *name, const char *mapset)
309 {
310  return G_fopen_old("cellhd", name, mapset);
311 }
312 
313 /*!
314  \brief Put reclass
315 
316  \param name map name
317  \param reclass pointer to Reclass structure
318 
319  \return -1 on error
320  \return 1 on success
321  */
322 int Rast_put_reclass(const char *name, const struct Reclass *reclass)
323 {
324  FILE *fd;
325  long min, max;
326  int found;
327  char buf1[GPATH_MAX], buf2[GNAME_MAX], *p;
328  char *xname;
329 
330  switch (reclass->type) {
331  case RECLASS_TABLE:
332  if (reclass->min > reclass->max || reclass->num <= 0) {
333  G_fatal_error(_("Illegal reclass request"));
334  return -1;
335  }
336  break;
337  default:
338  G_fatal_error(_("Illegal reclass type"));
339  return -1;
340  }
341 
342  fd = fopen_cellhd_new(name);
343  if (fd == NULL) {
344  G_warning(_("Unable to create header file for <%s@%s>"), name,
345  G_mapset());
346  return -1;
347  }
348 
349  fprintf(fd, "reclass\n");
350  fprintf(fd, "name: %s\n", reclass->name);
351  fprintf(fd, "mapset: %s\n", reclass->mapset);
352 
353  /* find first non-null entry */
354  for (min = 0; min < reclass->num; min++)
355  if (!Rast_is_c_null_value(&reclass->table[min]))
356  break;
357  /* find last non-zero entry */
358  for (max = reclass->num - 1; max >= 0; max--)
359  if (!Rast_is_c_null_value(&reclass->table[max]))
360  break;
361 
362  /*
363  * if the resultant table is empty, write out a dummy table
364  * else write out the table
365  * first entry is #min
366  * rest are translations for cat min+i
367  */
368  if (min > max)
369  fprintf(fd, "0\n");
370  else {
371  fprintf(fd, "#%ld\n", (long)reclass->min + min);
372  while (min <= max) {
373  if (Rast_is_c_null_value(&reclass->table[min]))
374  fprintf(fd, "%s\n", NULL_STRING);
375  else
376  fprintf(fd, "%ld\n", (long)reclass->table[min]);
377  min++;
378  }
379  }
380  fclose(fd);
381 
382  strcpy(buf2, reclass->name);
383  if ((p = strchr(buf2, '@')))
384  *p = 0;
385 
386  G_file_name_misc(buf1, "cell_misc", "reclassed_to", reclass->name,
387  reclass->mapset);
388 
389  fd = fopen(buf1, "a+");
390  if (fd == NULL) {
391 #if 0
392  G_warning(_("Unable to create dependency file in <%s@%s>"),
393  buf2, reclass->mapset);
394 #endif
395  return 1;
396  }
397 
398  G_fseek(fd, 0L, SEEK_SET);
399 
401  found = 0;
402  for (;;) {
403  char buf[GNAME_MAX + GMAPSET_MAX];
404 
405  if (!G_getl2(buf, sizeof(buf), fd))
406  break;
407  if (strcmp(xname, buf) == 0) {
408  found = 1;
409  break;
410  }
411  }
412 
413  if (!found)
414  fprintf(fd, "%s\n", xname);
415 
416  G_free(xname);
417  fclose(fd);
418 
419  return 1;
420 }
421 
422 static FILE *fopen_cellhd_new(const char *name)
423 {
424  return G_fopen_new("cellhd", name);
425 }
426 
427 /**
428  * \brief Get reclass table from header file
429  *
430  * If there is reading error due to the format, -1 is returned and,
431  * if error_message is not NULL, it will be set to a pointer to a newly
432  * allocated string containing an error message with the line where error
433  * was encountered.
434  *
435  * \param fd header file
436  * \param[out] reclass pointer to Reclass structure
437  * \param[out] error_message pointer to error message
438 
439  * \return 1 on success, -1 on format error, -2 on too many categories
440  */
441 static int get_reclass_table(FILE *fd, struct Reclass *reclass,
442  char **error_message)
443 {
444  char buf[128];
445  int n;
446  int first, null_str_size;
447  CELL cat;
448  long len;
449 
450  /*
451  * allocate the table, expanding as each entry is read
452  * note that G_realloc() will become G_malloc() if ptr in
453  * NULL
454  */
455  reclass->min = 0;
456  reclass->table = NULL;
457  null_str_size = strlen(NULL_STRING);
458  n = 0;
459  first = 1;
460  bool min_set = false;
461  while (fgets(buf, sizeof buf, fd)) {
462  if (first) {
463  first = 0;
464  if (sscanf(buf, "#%d", &cat) == 1) {
465  reclass->min = cat;
466  min_set = true;
467  continue;
468  }
469  }
470  if (strncmp(buf, NULL_STRING, null_str_size) == 0)
471  Rast_set_c_null_value(&cat, 1);
472  else {
473  if (sscanf(buf, "%d", &cat) != 1) {
474  if (reclass->table != NULL)
475  G_free(reclass->table);
476  if (error_message != NULL) {
477  if (min_set)
478  G_asprintf(error_message,
479  _("Reading integer failed on line: %s "
480  "(after reading min: %d)"),
481  buf, reclass->min);
482  else
483  G_asprintf(error_message,
484  _("First entry (min) not read yet and "
485  "reading integer failed on line: %s"),
486  buf);
487  }
488  return -1;
489  }
490  }
491  n++;
492  len = (long)n * sizeof(CELL);
493 
494  if (len != (int)len) { /* check for int overflow */
495  if (reclass->table != NULL)
496  G_free(reclass->table);
497  return -2;
498  }
499  reclass->table = (CELL *)G_realloc((char *)reclass->table, (int)len);
500  reclass->table[n - 1] = cat;
501  }
502  reclass->max = reclass->min + n - 1;
503  reclass->num = n;
504  return 1;
505 }
#define NULL
Definition: ccmath.h:32
char * G_file_name_misc(char *, const char *, const char *, const char *, const char *)
Builds full path names to GIS misc data files.
Definition: file_name.c:101
FILE * G_fopen_old(const char *, const char *, const char *)
Open a database file for reading.
Definition: gis/open.c:248
int G_getl2(char *, int, FILE *)
Gets a line of text from a file of any pedigree.
Definition: getl.c:60
void G_free(void *)
Free allocated memory.
Definition: gis/alloc.c:147
#define G_realloc(p, n)
Definition: defs/gis.h:96
FILE * G_fopen_old_misc(const char *, const char *, const char *, const char *)
open a database misc file for reading
Definition: open_misc.c:205
void void void void G_fatal_error(const char *,...) __attribute__((format(printf
void G_warning(const char *,...) __attribute__((format(printf
#define G_malloc(n)
Definition: defs/gis.h:94
void G_fseek(FILE *, off_t, int)
Change the file position of the stream.
Definition: gis/seek.c:50
const char * G_mapset(void)
Get current mapset name.
Definition: gis/mapset.c:33
int G_asprintf(char **, const char *,...) __attribute__((format(printf
char * G_fully_qualified_name(const char *, const char *)
Get fully qualified element name.
Definition: nme_in_mps.c:101
char * G_store(const char *)
Copy string to allocated memory.
Definition: strings.c:87
FILE * G_fopen_new(const char *, const char *)
Open a new database file.
Definition: gis/open.c:216
void Rast_set_c_null_value(CELL *, int)
To set a number of CELL raster values to NULL.
Definition: null_val.c:124
#define Rast_is_c_null_value(cellVal)
Definition: defs/raster.h:412
#define min(x, y)
Definition: draw2.c:29
#define max(x, y)
Definition: draw2.c:30
#define GMAPSET_MAX
Definition: gis.h:197
#define GPATH_MAX
Definition: gis.h:199
#define GNAME_MAX
Definition: gis.h:196
int CELL
Definition: gis.h:634
#define _(str)
Definition: glocale.h:10
const char * name
Definition: named_colr.c:6
#define strcpy
Definition: parson.c:62
double l
Definition: r_raster.c:39
#define RECLASS_TABLE
Definition: raster.h:7
int Rast_is_reclass(const char *name, const char *mapset, char *rname, char *rmapset)
Check if raster map is reclassified.
Definition: reclass.c:44
int Rast_get_reclass(const char *name, const char *mapset, struct Reclass *reclass)
Get reclass.
Definition: reclass.c:140
int Rast_put_reclass(const char *name, const struct Reclass *reclass)
Put reclass.
Definition: reclass.c:322
int Rast_is_reclassed_to(const char *name, const char *mapset, int *nrmaps, char ***rmaps)
Get child reclass maps list.
Definition: reclass.c:80
void Rast_free_reclass(struct Reclass *reclass)
Free Reclass structure.
Definition: reclass.c:198
Definition: raster.h:31
int num
Definition: raster.h:35
CELL * table
Definition: raster.h:38
char * mapset
Definition: raster.h:33
CELL max
Definition: raster.h:37
char * name
Definition: raster.h:32
int type
Definition: raster.h:34
CELL min
Definition: raster.h:36