diff --git a/src/raster/r.watersheds/Makefile b/src/raster/r.watersheds/Makefile new file mode 100644 index 0000000000..7f5c04f2cf --- /dev/null +++ b/src/raster/r.watersheds/Makefile @@ -0,0 +1,13 @@ +MODULE_TOPDIR = ../.. + +PGM = r.watersheds + +LIBES = $(RASTERLIB) $(VECTORLIB) $(DBMILIB) $(GISLIB) $(MATHLIB) +DEPENDENCIES = $(RASTERDEP) $(VECTORDEP) $(DBMIDEP) $(GISDEP) +EXTRA_LIBS = $(OPENMP_LIBPATH) $(OPENMP_LIB) +EXTRA_INC = $(VECT_INC) $(OPENMP_INCPATH) +EXTRA_CFLAGS = $(VECT_CFLAGS) $(OPENMP_CFLAGS) + +include $(MODULE_TOPDIR)/include/Make/Module.make + +default: cmd diff --git a/src/raster/r.watersheds/delineate.c b/src/raster/r.watersheds/delineate.c new file mode 100644 index 0000000000..037672dfb8 --- /dev/null +++ b/src/raster/r.watersheds/delineate.c @@ -0,0 +1,10 @@ +#include "global.h" + +void delineate(struct raster_map *dir_map, struct outlet_list *outlet_l, + int use_lessmem) +{ + if (use_lessmem) + delineate_lessmem(dir_map, outlet_l); + else + delineate_moremem(dir_map, outlet_l); +} diff --git a/src/raster/r.watersheds/delineate_funcs.h b/src/raster/r.watersheds/delineate_funcs.h new file mode 100644 index 0000000000..0bd99ce8a8 --- /dev/null +++ b/src/raster/r.watersheds/delineate_funcs.h @@ -0,0 +1,223 @@ +#include +#include "global.h" + +#define SHED(row, col) DIR(row, col) + +#ifdef USE_LESS_MEMORY +#define DELINEATE delineate_lessmem +#define GET_DIR(row, col) (DIR(row, col) & 0x7fffffff) +#define SET_NOTDONE(row, col) \ + do { \ + DIR(row, col) |= 0x80000000; \ + } while (0) +#define SET_DONE(row, col) \ + do { \ + DIR(row, col) &= 0x7fffffff; \ + } while (0) +#define IS_NOTDONE(row, col) (DIR(row, col) & 0x80000000) +#define IS_DONE(row, col) !IS_NOTDONE(row, col) +#else +#define DELINEATE delineate_moremem +#define GET_DIR(row, col) DIR(row, col) +#define DONE(row, col) done[INDEX(row, col)] +#define SET_DONE(row, col) \ + do { \ + DONE(row, col) = 1; \ + } while (0) +#define IS_NOTDONE(row, col) !DONE(row, col) +#define IS_DONE(row, col) DONE(row, col) +static char *done; +#endif + +#define E 1 +#define SE 2 +#define S 4 +#define SW 8 +#define W 16 +#define NW 32 +#define N 64 +#define NE 128 + +static int dir_checks[3][3] = {{SE, S, SW}, {E, 0, W}, {NE, N, NW}}; + +struct cell { + int row; + int col; +}; + +struct cell_stack { + struct cell *cells; + int n; + int nalloc; +}; + +static int nrows, ncols; + +static void trace_up(struct raster_map *, int, int, int, struct cell_stack *); +static void init_up_stack(struct cell_stack *); +static void free_up_stack(struct cell_stack *); +static void push_up(struct cell_stack *, struct cell *); +static struct cell pop_up(struct cell_stack *); + +void DELINEATE(struct raster_map *dir_map, struct outlet_list *outlet_l) +{ + int i, j; + + nrows = dir_map->nrows; + ncols = dir_map->ncols; + +#ifdef USE_LESS_MEMORY +#pragma omp parallel for schedule(dynamic) private(j) + for (i = 0; i < nrows; i++) { + for (j = 0; j < ncols; j++) + SET_NOTDONE(i, j); + } +#else + done = calloc((size_t)nrows * ncols, sizeof *done); +#endif + +#pragma omp parallel for schedule(dynamic) + for (i = 0; i < outlet_l->n; i++) { +#ifndef USE_LESS_MEMORY + SET_DONE(outlet_l->row[i], outlet_l->col[i]); +#endif + SHED(outlet_l->row[i], outlet_l->col[i]) = outlet_l->id[i]; + } + + /* loop through all outlets and delineate the subwatershed for each */ +#pragma omp parallel for schedule(dynamic) + for (i = 0; i < outlet_l->n; i++) { + struct cell_stack up_stack; + + init_up_stack(&up_stack); + + /* trace up flow directions */ + trace_up(dir_map, outlet_l->row[i], outlet_l->col[i], outlet_l->id[i], + &up_stack); + + free_up_stack(&up_stack); + } + +#pragma omp parallel for schedule(dynamic) private(j) + for (i = 0; i < nrows; i++) { + for (j = 0; j < ncols; j++) + if (IS_NOTDONE(i, j)) + Rast_set_c_null_value(&SHED(i, j), 1); + } + +#ifndef USE_LESS_MEMORY + free(done); +#endif +} + +static void trace_up(struct raster_map *dir_map, int row, int col, int id, + struct cell_stack *up_stack) +{ + int i, j; + int nup = 0; + int next_row = -1, next_col = -1; + + for (i = -1; i <= 1; i++) { + /* skip edge cells */ + if (row + i < 0 || row + i >= nrows) + continue; + + for (j = -1; j <= 1; j++) { + /* skip the current and edge cells */ + if ((i == 0 && j == 0) || col + j < 0 || col + j >= ncols) + continue; + + /* if a neighbor cell flows into the current cell, trace up + * further; we need to check if that neighbor cell has already been + * processed because we don't want to misinterpret a subwatershed + * ID as a direction; remember we're overwriting dir_map so it can + * have both directions and subwatershed IDs */ + if (GET_DIR(row + i, col + j) == dir_checks[i + 1][j + 1] && + IS_NOTDONE(row + i, col + j)) { + if (++nup == 1) { + /* climb up only to this cell at this time */ + next_row = row + i; + next_col = col + j; +#ifndef USE_LESS_MEMORY + SET_DONE(next_row, next_col); +#endif + SHED(next_row, next_col) = id; + } + else + /* if we found more than one upstream cell, we don't need + * to find more at this point */ + break; + } + } + /* if we found more than one upstream cell, we don't need to find more + * at this point */ + if (nup > 1) + break; + } + + if (!nup) { + /* reached a ridge cell; if there were any up cells to visit, let's go + * back or simply complete tracing */ + struct cell up; + + if (!up_stack->n) + return; + + up = pop_up(up_stack); + next_row = up.row; + next_col = up.col; + } + else if (nup > 1) { + /* if there are more up cells to visit, let's come back later */ + struct cell up; + + up.row = row; + up.col = col; + push_up(up_stack, &up); + } + + /* use gcc -O2 or -O3 flags for tail-call optimization + * (-foptimize-sibling-calls) */ + trace_up(dir_map, next_row, next_col, id, up_stack); +} + +static void init_up_stack(struct cell_stack *up_stack) +{ + up_stack->nalloc = up_stack->n = 0; + up_stack->cells = NULL; +} + +static void free_up_stack(struct cell_stack *up_stack) +{ + if (up_stack->cells) + free(up_stack->cells); + init_up_stack(up_stack); +} + +static void push_up(struct cell_stack *up_stack, struct cell *up) +{ + if (up_stack->n == up_stack->nalloc) { + up_stack->nalloc += REALLOC_INCREMENT; + up_stack->cells = realloc(up_stack->cells, + sizeof *up_stack->cells * up_stack->nalloc); + } + up_stack->cells[up_stack->n++] = *up; +} + +static struct cell pop_up(struct cell_stack *up_stack) +{ + struct cell up; + + if (up_stack->n > 0) { + up = up_stack->cells[--up_stack->n]; + if (up_stack->n == 0) + free_up_stack(up_stack); + else if (up_stack->n == up_stack->nalloc - REALLOC_INCREMENT) { + up_stack->nalloc -= REALLOC_INCREMENT; + up_stack->cells = realloc(up_stack->cells, sizeof *up_stack->cells * + up_stack->nalloc); + } + } + + return up; +} diff --git a/src/raster/r.watersheds/delineate_lessmem.c b/src/raster/r.watersheds/delineate_lessmem.c new file mode 100644 index 0000000000..350917fa7d --- /dev/null +++ b/src/raster/r.watersheds/delineate_lessmem.c @@ -0,0 +1,2 @@ +#define USE_LESS_MEMORY +#include "delineate_funcs.h" diff --git a/src/raster/r.watersheds/delineate_moremem.c b/src/raster/r.watersheds/delineate_moremem.c new file mode 100644 index 0000000000..5c2d4e57dd --- /dev/null +++ b/src/raster/r.watersheds/delineate_moremem.c @@ -0,0 +1 @@ +#include "delineate_funcs.h" diff --git a/src/raster/r.watersheds/gettimeofday.c b/src/raster/r.watersheds/gettimeofday.c new file mode 100644 index 0000000000..813517c011 --- /dev/null +++ b/src/raster/r.watersheds/gettimeofday.c @@ -0,0 +1,28 @@ +#ifdef _MSC_VER +#include +#include + +// https://stackoverflow.com/a/26085827/16079666 +int gettimeofday(struct timeval *tp, struct timezone *tzp) +{ + // Note: some broken versions only have 8 trailing zero's, the correct + // epoch has 9 trailing zero's This magic number is the number of 100 + // nanosecond intervals since January 1, 1601 (UTC) until 00:00:00 January + // 1, 1970 + static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t)file_time.dwLowDateTime); + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long)((time - EPOCH) / 10000000L); + tp->tv_usec = (long)(system_time.wMilliseconds * 1000); + + return 0; +} +#endif diff --git a/src/raster/r.watersheds/global.h b/src/raster/r.watersheds/global.h new file mode 100644 index 0000000000..40326dd391 --- /dev/null +++ b/src/raster/r.watersheds/global.h @@ -0,0 +1,61 @@ +#ifndef _GLOBAL_H_ +#define _GLOBAL_H_ + +#ifdef _MSC_VER +#include +/* gettimeofday.c */ +int gettimeofday(struct timeval *, struct timezone *); +#else +#include +#endif +#include + +#define REALLOC_INCREMENT 1024 + +#define INDEX(row, col) ((size_t)(row) * ncols + (col)) +#define DIR(row, col) dir_map->cells.c[INDEX(row, col)] + +struct raster_map { + RASTER_MAP_TYPE type; + size_t cell_size; + int nrows, ncols; + union { + void *v; + unsigned char *uint8; + CELL *c; + FCELL *f; + DCELL *d; + } cells; +}; + +struct outlet_list { + int nalloc, n; + int *row, *col; + int *id; +}; + +/* timeval_diff.c */ +long long timeval_diff(struct timeval *, struct timeval *, struct timeval *); + +/* raster_map.c */ +struct raster_map *read_direction(char *, char *); +void write_watersheds(char *, struct raster_map *); +void free_raster_map(struct raster_map *); + +/* outlet_list.c */ +void init_outlet_list(struct outlet_list *); +void reset_outlet_list(struct outlet_list *); +void free_outlet_list(struct outlet_list *); +void add_outlet(struct outlet_list *, double, double, int); +struct outlet_list *read_outlets(char *, char *, char *); + +/* delineate.c */ +void delineate(struct raster_map *, struct outlet_list *, int); + +/* delineate_lessmem.c */ +void delineate_lessmem(struct raster_map *, struct outlet_list *); + +/* delineate_moremem.c */ +void delineate_moremem(struct raster_map *, struct outlet_list *); + +#endif diff --git a/src/raster/r.watersheds/main.c b/src/raster/r.watersheds/main.c new file mode 100644 index 0000000000..6e7c0367dd --- /dev/null +++ b/src/raster/r.watersheds/main.c @@ -0,0 +1,181 @@ +/**************************************************************************** + * + * MODULE: r.watersheds + * + * AUTHOR(S): Huidae Cho + * + * PURPOSE: Delineate a large number of watersheds using the + * Memory-Efficient Watershed Delineation (MESHED) OpenMP + * parallel algorithm by Cho (2025). + * + * COPYRIGHT: (C) 2024 by Huidae Cho 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 +#ifdef _MSC_VER +#include +#else +#include +#endif +#ifdef _OPENMP +#include +#endif +#include +#include "global.h" + +int main(int argc, char *argv[]) +{ + struct GModule *module; + struct { + struct Option *dir; + struct Option *format; + struct Option *outlets; + struct Option *layer; + struct Option *idcol; + struct Option *wsheds; + struct Option *nprocs; + } opt; + struct { + struct Flag *use_less_memory; + } flag; + char *desc; + char *dir_name, *format, *outlets_name, *layer, *idcol, *wsheds_name; +#ifdef _OPENMP + int nprocs; +#endif + int use_less_memory; + struct raster_map *dir_map; + struct outlet_list *outlets_l; + struct timeval first_time, start_time, end_time; + + G_gisinit(argv[0]); + + module = G_define_module(); + G_add_keyword(_("raster")); + G_add_keyword(_("hydrology")); + G_add_keyword(_("watershed delineation")); + module->description = _("Delineate a large number of watersheds using the " + "Memory-Efficient Watershed Delineation (MESHED) " + "OpenMP parallel algorithm by Cho (2024)."); + + opt.dir = G_define_standard_option(G_OPT_R_INPUT); + opt.dir->key = "direction"; + opt.dir->description = _("Name of input direction raster map"); + + opt.format = G_define_option(); + opt.format->type = TYPE_STRING; + opt.format->key = "format"; + opt.format->label = _("Format of input direction raster map"); + opt.format->required = YES; + opt.format->options = "auto,degree,45degree,power2"; + opt.format->answer = "auto"; + G_asprintf(&desc, "auto;%s;degree;%s;45degree;%s;power2;%s", + _("auto-detect direction format"), _("degrees CCW from East"), + _("degrees CCW from East divided by 45 (e.g. r.watershed)"), + _("powers of 2 CW from East (e.g., r.terraflow, ArcGIS)")); + opt.format->descriptions = desc; + + opt.outlets = G_define_standard_option(G_OPT_V_INPUT); + opt.outlets->key = "outlets"; + opt.outlets->required = YES; + opt.outlets->label = _("Name of input outlets vector map"); + + opt.layer = G_define_standard_option(G_OPT_V_FIELD); + + opt.idcol = G_define_standard_option(G_OPT_DB_COLUMN); + opt.idcol->description = _("Name of attribute column for watershed IDs " + "(using a non-default column is slower)"); + opt.idcol->answer = GV_KEY_COLUMN; + + opt.wsheds = G_define_standard_option(G_OPT_R_OUTPUT); + opt.wsheds->description = _("Name for output watersheds raster map"); + +#ifdef _OPENMP + opt.nprocs = G_define_standard_option(G_OPT_M_NPROCS); +#endif + + flag.use_less_memory = G_define_flag(); + flag.use_less_memory->key = 'm'; + flag.use_less_memory->label = _("Use less memory"); + + if (G_parser(argc, argv)) + exit(EXIT_FAILURE); + + dir_name = opt.dir->answer; + format = opt.format->answer; + outlets_name = opt.outlets->answer; + layer = opt.layer->answer; + idcol = opt.idcol->answer; + wsheds_name = opt.wsheds->answer; + +#ifdef _OPENMP + nprocs = atoi(opt.nprocs->answer); + if (nprocs < 1) + G_fatal_error(_("<%s> must be >= 1"), opt.nprocs->key); + + omp_set_num_threads(nprocs); +#pragma omp parallel +#pragma omp single + nprocs = omp_get_num_threads(); + G_message(n_("Using %d thread for serial computation", + "Using %d threads for parallel computation", nprocs), + nprocs); +#endif + + use_less_memory = flag.use_less_memory->answer; + + /* read direction raster */ + G_message(_("Reading flow direction raster <%s>..."), dir_name); + gettimeofday(&start_time, NULL); + first_time = start_time; + + dir_map = read_direction(dir_name, format); + + gettimeofday(&end_time, NULL); + G_message(_("Input time for flow direction: %f seconds"), + timeval_diff(NULL, &end_time, &start_time) / 1e6); + + /* read outlets vector */ + G_message(_("Reading outlets vector <%s>..."), outlets_name); + gettimeofday(&start_time, NULL); + + outlets_l = read_outlets(outlets_name, layer, idcol); + + gettimeofday(&end_time, NULL); + G_message(_("Input time for outlets: %f seconds"), + timeval_diff(NULL, &end_time, &start_time) / 1e6); + + /* watershed delineation */ + G_message(_("Delineating watersheds...")); + gettimeofday(&start_time, NULL); + + delineate(dir_map, outlets_l, use_less_memory); + + free_outlet_list(outlets_l); + + gettimeofday(&end_time, NULL); + G_message(_("Compute time for watershed delineation: %f seconds"), + timeval_diff(NULL, &end_time, &start_time) / 1e6); + + /* write out buffer to watersheds raster */ + G_message(_("Writing watersheds raster <%s>..."), wsheds_name); + gettimeofday(&start_time, NULL); + + write_watersheds(wsheds_name, dir_map); + + free_raster_map(dir_map); + + gettimeofday(&end_time, NULL); + G_message(_("Output time for watershed delineation: %f seconds"), + timeval_diff(NULL, &end_time, &start_time) / 1e6); + + G_message(_("Total elapsed time: %f seconds"), + timeval_diff(NULL, &end_time, &first_time) / 1e6); + + exit(EXIT_SUCCESS); +} diff --git a/src/raster/r.watersheds/outlet_list.c b/src/raster/r.watersheds/outlet_list.c new file mode 100644 index 0000000000..72e262357a --- /dev/null +++ b/src/raster/r.watersheds/outlet_list.c @@ -0,0 +1,116 @@ +#include +#include +#include "global.h" + +static struct Cell_head window; + +void init_outlet_list(struct outlet_list *ol) +{ + G_get_set_window(&window); + + ol->nalloc = ol->n = 0; + ol->row = ol->col = NULL; + ol->id = NULL; +} + +void reset_outlet_list(struct outlet_list *ol) +{ + ol->n = 0; +} + +void free_outlet_list(struct outlet_list *ol) +{ + if (ol->row) + free(ol->row); + if (ol->col) + free(ol->col); + if (ol->id) + free(ol->id); + init_outlet_list(ol); +} + +/* adapted from r.path */ +void add_outlet(struct outlet_list *ol, double x, double y, int id) +{ + if (ol->n == ol->nalloc) { + ol->nalloc += REALLOC_INCREMENT; + ol->row = realloc(ol->row, sizeof *ol->row * ol->nalloc); + ol->col = realloc(ol->col, sizeof *ol->col * ol->nalloc); + ol->id = realloc(ol->id, sizeof *ol->id * ol->nalloc); + if (!ol->row || !ol->col || !ol->id) + G_fatal_error(_("Unable to increase outlet list")); + } + ol->row[ol->n] = (int)Rast_northing_to_row(y, &window); + ol->col[ol->n] = (int)Rast_easting_to_col(x, &window); + ol->id[ol->n] = id; + ol->n++; +} + +struct outlet_list *read_outlets(char *outlets_name, char *layer, char *idcol) +{ + struct outlet_list *outlets_l = G_malloc(sizeof *outlets_l); + struct Map_info Map; + dbDriver *driver = NULL; + struct field_info *Fi; + struct line_pnts *Points; + struct line_cats *Cats; + int field; + int nlines, line; + + if (Vect_open_old2(&Map, outlets_name, "", layer) < 0) + G_fatal_error(_("Unable to open vector map <%s>"), outlets_name); + + field = Vect_get_field_number(&Map, layer); + + /* avoid database queries for the default cat column; it's expensive */ + if (strcmp(idcol, GV_KEY_COLUMN) != 0) { + Fi = Vect_get_field(&Map, field); + if (!(driver = db_start_driver_open_database( + Fi->driver, Vect_subst_var(Fi->database, &Map)))) + G_fatal_error("Unable to start db driver"); + if (db_column_Ctype(driver, Fi->table, idcol) != DB_C_TYPE_INT) + G_fatal_error( + _("Column <%s> in vector map <%s> must be of integer type"), + idcol, outlets_name); + } + + Points = Vect_new_line_struct(); + Cats = Vect_new_cats_struct(); + nlines = Vect_get_num_lines(&Map); + init_outlet_list(outlets_l); + + for (line = 1; line <= nlines; line++) { + int ltype, cat, id; + + G_percent(line, nlines, 1); + + ltype = Vect_read_line(&Map, Points, Cats, line); + Vect_cat_get(Cats, field, &cat); + + if (ltype != GV_POINT || cat < 0) + continue; + + if (driver) { + dbValue val; + + if (db_select_value(driver, Fi->table, Fi->key, cat, idcol, &val) < + 0) + G_fatal_error( + _("Unable to read column <%s> in vector map <%s>"), idcol, + outlets_name); + + id = db_get_value_int(&val); + } + else + id = cat; + + add_outlet(outlets_l, Points->x[0], Points->y[0], id); + } + + if (driver) + db_close_database_shutdown_driver(driver); + + Vect_close(&Map); + + return outlets_l; +} diff --git a/src/raster/r.watersheds/r.watersheds.html b/src/raster/r.watersheds/r.watersheds.html new file mode 100644 index 0000000000..41df75ac2e --- /dev/null +++ b/src/raster/r.watersheds/r.watersheds.html @@ -0,0 +1,89 @@ +

DESCRIPTION

+ +r.watersheds delineates a large number of watersheds from a flow +direction raster map and an outlets vector map using the Memory-Efficient +Watershed Delineation (MESHED) OpenMP parallel algorithm by Cho (2025). + +

NOTES

+ +r.watersheds uses a flow direction raster map and an outlets vector +map to delineate a large number of watersheds in parallel using OpenMP. + +

The module recognizes three different formats of flow directions: +

+degree +
+ +r.watershed (singular) can be used to create an input flow direction +raster map. It can also create watersheds, but it uses an elevation map instead +of a flow direction map, which is actually one of its outputs, and does not +take outlets as input. r.watersheds is more similar to +r.water.outlet and r.stream.basins. Both modules take an +input flow direction map from r.watershed, but r.water.outlet +can delineate a watershed for one outlet point at a time and +r.stream.basins is a sequential module for multiple watersheds. Unlike +r.stream.basins, r.watersheds can use a column for watershed +IDs, but using a non-default column is slower than using the default category +(cat) column because of database queries. + +

For comparisons, using an i7-1370P CPU with 64GB memory and a 30-meter flow +direction map for the entire Texas (1.8 billion cells), r.watersheds +took 1 minute 27 seconds to delineate the entire state using 60,993 outlet +cells draining away (see below how to extract draining cells) while +r.stream.basins 5 minutes 28 seconds, both using the category column. +However, r.watersheds with a non-category column took 6 minutes 21 +seconds because of heavy database queries. + +

EXAMPLES

+ +These examples use the North Carolina sample dataset. + +

Calculate flow accumulation using r.watershed and +r.watersheds: +

+# set computational region
+g.region -ap raster=elevation
+
+# calculate drainage directions using r.watershed
+r.watershed -s elevation=elevation drainage=drain
+
+# extract draining cells
+r.mapcalc ex="dcells=if(\
+        (isnull(drain[-1,-1])&&abs(drain)==3)||\
+        (isnull(drain[-1,0])&&abs(drain)==2)||\
+        (isnull(drain[-1,1])&&abs(drain)==1)||\
+        (isnull(drain[0,-1])&&abs(drain)==4)||\
+        (isnull(drain[0,1])&&abs(drain)==8)||\
+        (isnull(drain[1,-1])&&abs(drain)==5)||\
+        (isnull(drain[1,0])&&abs(drain)==6)||\
+        (isnull(drain[1,1])&&abs(drain)==7),1,null())"
+r.to.vect input=dcells type=point output=dcells
+
+# delineate all watersheds using r.watersheds
+r.watersheds dir=drain outlets=dcells output=wsheds nprocs=$(nproc)
+
+ +
+ +
+ +

SEE ALSO

+ + +r.flowaccumulation, +r.accumulate, +r.watershed, +r.stream.extract, +r.stream.distance + + +

REFERENCES

+ +Huidae Cho, January 2025. Avoid Backtracking and Burn Your Inputs: +CONUS-Scale Watershed Delineation Using OpenMP. Environmental Modelling +& Software 183, 106244. +doi:10.1016/j.envsoft.2024.106244. + +

AUTHOR

+ +Huidae Cho, New Mexico State University diff --git a/src/raster/r.watersheds/r_watersheds_formats.png b/src/raster/r.watersheds/r_watersheds_formats.png new file mode 100644 index 0000000000..50c54bc719 Binary files /dev/null and b/src/raster/r.watersheds/r_watersheds_formats.png differ diff --git a/src/raster/r.watersheds/r_watersheds_nc_example.png b/src/raster/r.watersheds/r_watersheds_nc_example.png new file mode 100644 index 0000000000..1df8260f1e Binary files /dev/null and b/src/raster/r.watersheds/r_watersheds_nc_example.png differ diff --git a/src/raster/r.watersheds/raster_map.c b/src/raster/r.watersheds/raster_map.c new file mode 100644 index 0000000000..70be7e7a31 --- /dev/null +++ b/src/raster/r.watersheds/raster_map.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include "global.h" + +#define DIR_UNKNOWN 0 +#define DIR_DEG 1 +#define DIR_DEG45 2 +#define DIR_POW2 3 + +struct raster_map *read_direction(char *dir_name, char *format) +{ + int dir_fd, dir_format; + struct Range dir_range; + CELL dir_min, dir_max, *dir_buf; + struct raster_map *dir_map; + int nrows, ncols, row, col; + + dir_fd = Rast_open_old(dir_name, ""); + if (Rast_get_map_type(dir_fd) != CELL_TYPE) + G_fatal_error(_("Invalid direction map <%s>"), dir_name); + + /* adapted from r.path */ + if (Rast_read_range(dir_name, "", &dir_range) < 0) + G_fatal_error(_("Unable to read range file")); + Rast_get_range_min_max(&dir_range, &dir_min, &dir_max); + if (dir_max <= 0) + G_fatal_error(_("Invalid direction map <%s>"), dir_name); + + dir_format = DIR_UNKNOWN; + if (strcmp(format, "degree") == 0) { + if (dir_max > 360) + G_fatal_error(_("Directional degrees cannot be > 360")); + dir_format = DIR_DEG; + } + else if (strcmp(format, "45degree") == 0) { + if (dir_max > 8) + G_fatal_error(_("Directional degrees divided by 45 cannot be > 8")); + dir_format = DIR_DEG45; + } + else if (strcmp(format, "power2") == 0) { + if (dir_max > 128) + G_fatal_error(_("Powers of 2 cannot be > 128")); + dir_format = DIR_POW2; + } + else if (strcmp(format, "auto") == 0) { + if (dir_max <= 8) { + dir_format = DIR_DEG45; + G_important_message(_("Flow direction format assumed to be " + "degrees CCW from East divided by 45")); + } + else if (dir_max <= 128) { + dir_format = DIR_POW2; + G_important_message(_("Flow direction format assumed to be " + "powers of 2 CW from East")); + } + else if (dir_max <= 360) { + dir_format = DIR_DEG; + G_important_message( + _("Flow direction format assumed to be degrees CCW from East")); + } + else + G_fatal_error( + _("Unable to detect format of input direction map <%s>"), + dir_name); + } + if (dir_format == DIR_UNKNOWN) + G_fatal_error(_("Invalid direction format '%s'"), format); + /* end of r.path */ + + nrows = Rast_window_rows(); + ncols = Rast_window_cols(); + + dir_map = G_malloc(sizeof *dir_map); + dir_map->type = CELL_TYPE; + dir_map->cell_size = Rast_cell_size(dir_map->type); + dir_map->nrows = nrows; + dir_map->ncols = ncols; + dir_map->cells.v = G_malloc(dir_map->cell_size * nrows * ncols); + dir_buf = G_malloc(dir_map->cell_size * ncols); + + for (row = 0; row < nrows; row++) { + G_percent(row, nrows, 1); + Rast_get_c_row(dir_fd, dir_buf, row); + switch (dir_format) { + case DIR_DEG: + for (col = 0; col < ncols; col++) + if (!Rast_is_c_null_value(&dir_buf[col])) + DIR(row, col) = pow(2, abs(dir_buf[col] / 45.)); + break; + case DIR_DEG45: + for (col = 0; col < ncols; col++) + if (!Rast_is_c_null_value(&dir_buf[col])) + DIR(row, col) = pow(2, 8 - abs(dir_buf[col])); + break; + default: + for (col = 0; col < ncols; col++) + if (!Rast_is_c_null_value(&dir_buf[col])) + DIR(row, col) = abs(dir_buf[col]); + break; + } + } + G_percent(1, 1, 1); + G_free(dir_buf); + Rast_close(dir_fd); + + return dir_map; +} + +void write_watersheds(char *wsheds_name, struct raster_map *wsheds_map) +{ + int wsheds_fd = Rast_open_new(wsheds_name, wsheds_map->type); + int row; + struct History hist; + + for (row = 0; row < wsheds_map->nrows; row++) { + G_percent(row, wsheds_map->nrows, 1); + Rast_put_row(wsheds_fd, + (char *)wsheds_map->cells.v + + wsheds_map->cell_size * wsheds_map->ncols * row, + wsheds_map->type); + } + G_percent(1, 1, 1); + Rast_close(wsheds_fd); + + /* write history */ + Rast_put_cell_title(wsheds_name, _("Watersheds")); + Rast_short_history(wsheds_name, "raster", &hist); + Rast_command_history(&hist); + Rast_write_history(wsheds_name, &hist); +} + +void free_raster_map(struct raster_map *rast_map) +{ + G_free(rast_map->cells.v); + G_free(rast_map); +} diff --git a/src/raster/r.watersheds/timeval_diff.c b/src/raster/r.watersheds/timeval_diff.c new file mode 100644 index 0000000000..bfdb2e26c2 --- /dev/null +++ b/src/raster/r.watersheds/timeval_diff.c @@ -0,0 +1,26 @@ +#include +#ifdef _MSC_VER +#include +#else +#include +#endif + +/* https://www.linuxquestions.org/questions/programming-9/how-to-calculate-time-difference-in-milliseconds-in-c-c-711096/#post3475339 + */ +long long timeval_diff(struct timeval *difference, struct timeval *end_time, + struct timeval *start_time) +{ + struct timeval temp_diff; + + if (difference == NULL) + difference = &temp_diff; + difference->tv_sec = end_time->tv_sec - start_time->tv_sec; + difference->tv_usec = end_time->tv_usec - start_time->tv_usec; + + /* Using while instead of if below makes the code slightly more robust. */ + while (difference->tv_usec < 0) { + difference->tv_usec += 1000000; + difference->tv_sec -= 1; + } + return 1000000LL * difference->tv_sec + difference->tv_usec; +}