diff --git a/vector/v.edit/args.c b/vector/v.edit/args.c index 4ed5b18c338..ec161e99ff9 100644 --- a/vector/v.edit/args.c +++ b/vector/v.edit/args.c @@ -31,7 +31,6 @@ int parser(int argc, char *argv[], struct GParams *params, params->tool = G_define_option(); params->tool->key = "tool"; params->tool->type = TYPE_STRING; - params->tool->required = YES; params->tool->multiple = NO; params->tool->description = _("Tool"); desc_tool = NULL; @@ -225,6 +224,24 @@ int parser(int argc, char *argv[], struct GParams *params, params->extend_parallel->description = _("Connect parallel lines (using extend tools and threshold distance)"); + params->batch = G_define_standard_option(G_OPT_F_INPUT); + params->batch->key = "batch"; + params->batch->required = NO; + params->batch->label = _("Name of input file for batch editing"); + params->batch->description = _("'-' for standard input"); + + params->sep = G_define_standard_option(G_OPT_F_SEP); + params->sep->label = _("Field separator for batch input file"); + params->sep->description = _("Special characters: pipe, comma, space, tab"); + + G_option_required(params->tool, params->batch, NULL); + G_option_excludes(params->batch, params->tool, params->in, params->move, + params->id, params->cat, params->coord, params->bbox, + params->poly, params->where, params->query, params->snap, + params->zbulk, params->reverse, params->close, + params->header, params->topo, params->move_first, + params->extend_parallel, NULL); + if (G_parser(argc, argv)) exit(EXIT_FAILURE); @@ -242,119 +259,128 @@ int parser(int argc, char *argv[], struct GParams *params, /* check that the given arguments makes sense together */ - if (strcmp(params->tool->answer, "create") == 0) { - *action_mode = MODE_CREATE; - } - else if (strcmp(params->tool->answer, "add") == 0) { - *action_mode = MODE_ADD; - } - else if (strcmp(params->tool->answer, "delete") == 0) { - *action_mode = MODE_DEL; - } - else if (strcmp(params->tool->answer, "move") == 0) { - *action_mode = MODE_MOVE; - } - else if (strcmp(params->tool->answer, "merge") == 0) { - *action_mode = MODE_MERGE; - } - else if (strcmp(params->tool->answer, "break") == 0) { - *action_mode = MODE_BREAK; - } - else if (strcmp(params->tool->answer, "connect") == 0) { - *action_mode = MODE_CONNECT; - } - else if (strcmp(params->tool->answer, "extend") == 0) { - *action_mode = MODE_EXTEND; - } - else if (strcmp(params->tool->answer, "extendstart") == 0) { - *action_mode = MODE_EXTEND_START; - } - else if (strcmp(params->tool->answer, "extendend") == 0) { - *action_mode = MODE_EXTEND_END; - } - else if (strcmp(params->tool->answer, "vertexadd") == 0) { - *action_mode = MODE_VERTEX_ADD; - } - else if (strcmp(params->tool->answer, "vertexdel") == 0) { - *action_mode = MODE_VERTEX_DELETE; - } - else if (strcmp(params->tool->answer, "vertexmove") == 0) { - *action_mode = MODE_VERTEX_MOVE; - } - else if (strcmp(params->tool->answer, "select") == 0) { - *action_mode = MODE_SELECT; - } - else if (strcmp(params->tool->answer, "catadd") == 0) { - *action_mode = MODE_CATADD; - } - else if (strcmp(params->tool->answer, "catdel") == 0) { - *action_mode = MODE_CATDEL; - } - else if (strcmp(params->tool->answer, "copy") == 0) { - *action_mode = MODE_COPY; - } - else if (strcmp(params->tool->answer, "snap") == 0) { - *action_mode = MODE_SNAP; - } - else if (strcmp(params->tool->answer, "flip") == 0) { - *action_mode = MODE_FLIP; - } - else if (strcmp(params->tool->answer, "zbulk") == 0) { - *action_mode = MODE_ZBULK; - } - else if (strcmp(params->tool->answer, "chtype") == 0) { - *action_mode = MODE_CHTYPE; - } - else if (strcmp(params->tool->answer, "areadel") == 0) { - *action_mode = MODE_AREA_DEL; - } - else { - G_fatal_error(_("Operation '%s' not implemented"), - params->tool->answer); - } - - if ((*action_mode != MODE_CREATE && *action_mode != MODE_ADD && - *action_mode != MODE_ZBULK) && - (params->cat->answers == NULL) && (params->coord->answers == NULL) && - (params->poly->answers == NULL) && (params->id->answers == NULL) && - (params->bbox->answers == NULL) && (params->where->answer == NULL) && - (params->query->answer == NULL)) { - G_fatal_error(_("At least one option from %s must be specified"), - "cats, ids, coords, bbox, polygon, where, query"); - } + if (params->tool->answer) { + if (strcmp(params->tool->answer, "create") == 0) { + *action_mode = MODE_CREATE; + } + else if (strcmp(params->tool->answer, "add") == 0) { + *action_mode = MODE_ADD; + } + else if (strcmp(params->tool->answer, "delete") == 0) { + *action_mode = MODE_DEL; + } + else if (strcmp(params->tool->answer, "move") == 0) { + *action_mode = MODE_MOVE; + } + else if (strcmp(params->tool->answer, "merge") == 0) { + *action_mode = MODE_MERGE; + } + else if (strcmp(params->tool->answer, "break") == 0) { + *action_mode = MODE_BREAK; + } + else if (strcmp(params->tool->answer, "connect") == 0) { + *action_mode = MODE_CONNECT; + } + else if (strcmp(params->tool->answer, "extend") == 0) { + *action_mode = MODE_EXTEND; + } + else if (strcmp(params->tool->answer, "extendstart") == 0) { + *action_mode = MODE_EXTEND_START; + } + else if (strcmp(params->tool->answer, "extendend") == 0) { + *action_mode = MODE_EXTEND_END; + } + else if (strcmp(params->tool->answer, "vertexadd") == 0) { + *action_mode = MODE_VERTEX_ADD; + } + else if (strcmp(params->tool->answer, "vertexdel") == 0) { + *action_mode = MODE_VERTEX_DELETE; + } + else if (strcmp(params->tool->answer, "vertexmove") == 0) { + *action_mode = MODE_VERTEX_MOVE; + } + else if (strcmp(params->tool->answer, "select") == 0) { + *action_mode = MODE_SELECT; + } + else if (strcmp(params->tool->answer, "catadd") == 0) { + *action_mode = MODE_CATADD; + } + else if (strcmp(params->tool->answer, "catdel") == 0) { + *action_mode = MODE_CATDEL; + } + else if (strcmp(params->tool->answer, "copy") == 0) { + *action_mode = MODE_COPY; + } + else if (strcmp(params->tool->answer, "snap") == 0) { + *action_mode = MODE_SNAP; + } + else if (strcmp(params->tool->answer, "flip") == 0) { + *action_mode = MODE_FLIP; + } + else if (strcmp(params->tool->answer, "zbulk") == 0) { + *action_mode = MODE_ZBULK; + } + else if (strcmp(params->tool->answer, "chtype") == 0) { + *action_mode = MODE_CHTYPE; + } + else if (strcmp(params->tool->answer, "areadel") == 0) { + *action_mode = MODE_AREA_DEL; + } + else { + G_fatal_error(_("Operation '%s' not implemented"), + params->tool->answer); + } - if (*action_mode == MODE_MOVE || *action_mode == MODE_VERTEX_MOVE) { - if (params->move->answers == NULL) { - G_fatal_error(_("Tool %s requires option %s"), params->tool->answer, - params->move->key); + if ((*action_mode != MODE_CREATE && *action_mode != MODE_ADD && + *action_mode != MODE_ZBULK) && + (params->cat->answers == NULL) && + (params->coord->answers == NULL) && + (params->poly->answers == NULL) && (params->id->answers == NULL) && + (params->bbox->answers == NULL) && + (params->where->answer == NULL) && + (params->query->answer == NULL) && + (params->batch->answer == NULL)) { + G_fatal_error( + _("At least one option from %s must be specified"), + "cats, ids, coords, bbox, polygon, where, query, batch"); } - } - if (*action_mode == MODE_VERTEX_ADD || *action_mode == MODE_VERTEX_DELETE || - *action_mode == MODE_VERTEX_MOVE) { - if (params->coord->answers == NULL) { - G_fatal_error(_("Tool %s requires option %s"), params->tool->answer, - params->coord->key); + if (*action_mode == MODE_MOVE || *action_mode == MODE_VERTEX_MOVE) { + if (params->move->answers == NULL) { + G_fatal_error(_("Tool %s requires option %s"), + params->tool->answer, params->move->key); + } } - } - if (*action_mode == MODE_CATADD || *action_mode == MODE_CATDEL) { - if (params->cat->answers == NULL) { - G_fatal_error(_("Tool %s requires option %s"), params->tool->answer, - params->cat->key); + if (*action_mode == MODE_VERTEX_ADD || + *action_mode == MODE_VERTEX_DELETE || + *action_mode == MODE_VERTEX_MOVE) { + if (params->coord->answers == NULL) { + G_fatal_error(_("Tool %s requires option %s"), + params->tool->answer, params->coord->key); + } } - } - if (*action_mode == MODE_ZBULK) { - if (params->bbox->answers == NULL) { - G_fatal_error(_("Tool %s requires option %s"), params->tool->answer, - params->bbox->key); + if (*action_mode == MODE_CATADD || *action_mode == MODE_CATDEL) { + if (params->cat->answers == NULL) { + G_fatal_error(_("Tool %s requires option %s"), + params->tool->answer, params->cat->key); + } } - if (params->zbulk->answers == NULL) { - G_fatal_error(_("Tool %s requires option %s"), params->tool->answer, - params->zbulk->key); + + if (*action_mode == MODE_ZBULK) { + if (params->bbox->answers == NULL) { + G_fatal_error(_("Tool %s requires option %s"), + params->tool->answer, params->bbox->key); + } + if (params->zbulk->answers == NULL) { + G_fatal_error(_("Tool %s requires option %s"), + params->tool->answer, params->zbulk->key); + } } } + else + *action_mode = MODE_BATCH; return 1; } diff --git a/vector/v.edit/batch.c b/vector/v.edit/batch.c new file mode 100644 index 00000000000..a666e5c09bc --- /dev/null +++ b/vector/v.edit/batch.c @@ -0,0 +1,336 @@ +#include "global.h" + +#define GET_FLAG(flag) (flags && strchr(flags, flag)) + +#define MAX_COLUMNS 13 +#define NUM_TOOLS 21 + +#define COLUMN_TOOL 0 +#define COLUMN_FLAGS 1 +#define COLUMN_INPUT 2 +#define COLUMN_MOVE 3 +#define COLUMN_IDS 4 +#define COLUMN_CATS 5 +#define COLUMN_COORDS 6 +#define COLUMN_BBOX 7 +#define COLUMN_POLYGON 8 +#define COLUMN_WHERE 9 +#define COLUMN_QUERY 10 +#define COLUMN_SNAP 11 +#define COLUMN_ZBULK 12 + +static char *read_column(char *, char, char **); + +int batch_edit(struct Map_info *Map, struct Map_info **BgMap, int nbgmaps, + const char *file, char sep, struct SelectParams *selparams) +{ + char *col_names[MAX_COLUMNS] = { + "tool", "flags", "input", "move", "ids", "cats", "coords", + "bbox", "polygon", "where", "query", "snap", "zbulk"}; + int col_nums[MAX_COLUMNS] = { + COLUMN_TOOL, COLUMN_FLAGS, COLUMN_INPUT, COLUMN_MOVE, COLUMN_IDS, + COLUMN_CATS, COLUMN_COORDS, COLUMN_BBOX, COLUMN_POLYGON, COLUMN_WHERE, + COLUMN_QUERY, COLUMN_SNAP, COLUMN_ZBULK}; + char *tool_names[NUM_TOOLS] = { + "add", "delete", "copy", "move", "flip", + "catadd", "catdel", "merge", "break", "snap", + "connect", "extend", "extendstart", "extendend", "chtype", + "vertexadd", "vertexdel", "vertexmove", "areadel", "zbulk", + "select"}; + enum mode tool_modes[NUM_TOOLS] = { + MODE_ADD, MODE_DEL, MODE_COPY, MODE_MOVE, + MODE_FLIP, MODE_CATADD, MODE_CATDEL, MODE_MERGE, + MODE_BREAK, MODE_SNAP, MODE_CONNECT, MODE_EXTEND, + MODE_EXTEND_START, MODE_EXTEND_END, MODE_CHTYPE, MODE_VERTEX_ADD, + MODE_VERTEX_DELETE, MODE_VERTEX_MOVE, MODE_AREA_DEL, MODE_ZBULK, + MODE_SELECT}; + FILE *fp; + char buf[1024]; + int line = 0, first = 1; + int cols[MAX_COLUMNS], ncols = 0; + double *thresh = selparams->thresh; + struct EditParams editparams; + int total_ret = 0; + + editparams.thresh = thresh; + + if (strcmp(file, "-") != 0) { + if (!(fp = fopen(file, "r"))) + G_fatal_error(_("Unable to open file <%s>"), file); + } + else + fp = stdin; + + while (fgets(buf, 1024, fp)) { + char *p, *pnext; + char *flags, *input; + enum mode action_mode; + struct ilist *List; + int ret = 0; + int i; + + line++; + + selparams->ids = selparams->cats = selparams->coords = selparams->bbox = + selparams->polygon = selparams->where = selparams->query = NULL; + editparams.input = NULL; + editparams.move = editparams.cats = editparams.coords = + editparams.snap = editparams.zbulk = editparams.bbox = NULL; + flags = input = NULL; + + /* remove newline */ + if ((p = strchr(buf, '\n'))) { + *p = 0; + if ((p = strchr(buf, '\r'))) + *p = 0; + } + else if ((p = strchr(buf, '\r'))) + *p = 0; + + /* find the first non-whitespace character */ + p = strchr(buf, 0); + while (--p >= buf && (*p == ' ' || *p == '\t')) + ; + + /* an empty line starts a new table */ + if (p < buf) { + first = 1; + continue; + } + + p = buf; + + /* read batch columns */ + if (first) { + int bit_cols = 0, bit_col; + + ncols = 0; + + while ((p = read_column(p, sep, &pnext))) { + int known_col = 0; + + for (i = 0; i < MAX_COLUMNS; i++) { + if (strcmp(p, col_names[i]) == 0) { + bit_col = 1 << col_nums[i]; + if (bit_cols & bit_col) + G_fatal_error(_("Duplicate batch column '%s' not " + "allowed in line %d"), + col_names[i], line); + bit_cols |= bit_col; + cols[ncols++] = col_nums[i]; + known_col = 1; + } + } + if (!known_col) + G_fatal_error(_("Unknown batch column '%s' in line %d"), p, + line); + + if (!pnext) + break; + p = pnext; + } + + if (!p) + G_fatal_error(_("Illegal batch column in line %d"), line); + + if (!(bit_cols & 1 << COLUMN_TOOL)) + G_fatal_error( + _("Required batch column '%s' missing in line %d"), + col_names[COLUMN_TOOL], line); + + first = 0; + continue; + } + + G_message(_("Batch line %d..."), line); + + i = 0; + while ((p = read_column(p, sep, &pnext))) { + int j; + + if (i >= ncols) + G_fatal_error(_("Too many batch columns in line %d"), line); + + if (*p) { + switch (cols[i]) { + case COLUMN_TOOL: + for (j = 0; j < NUM_TOOLS; j++) + if (strcmp(p, tool_names[j]) == 0) { + action_mode = tool_modes[j]; + break; + } + if (j == NUM_TOOLS) + G_fatal_error(_("Unsupported tool '%s' in line %d"), p, + line); + break; + case COLUMN_FLAGS: + flags = p; + break; + case COLUMN_INPUT: + input = p; + break; + case COLUMN_MOVE: + editparams.move = p; + break; + case COLUMN_IDS: + selparams->ids = p; + break; + case COLUMN_CATS: + editparams.cats = selparams->cats = p; + break; + case COLUMN_COORDS: + editparams.coords = selparams->coords = p; + break; + case COLUMN_BBOX: + editparams.bbox = selparams->bbox = p; + break; + case COLUMN_POLYGON: + selparams->polygon = p; + break; + case COLUMN_WHERE: + selparams->where = p; + break; + case COLUMN_QUERY: + selparams->query = p; + break; + case COLUMN_SNAP: + editparams.snap = p; + break; + case COLUMN_ZBULK: + editparams.zbulk = p; + break; + } + } + + i++; + if (!pnext) + break; + p = pnext; + } + + if (!p) + G_fatal_error(_("Illegal batch column in line %d"), line); + + if (i < ncols) + G_fatal_error(_("Too few batch columns in line %d"), line); + + editparams.close = action_mode == MODE_ADD && GET_FLAG('c'); + editparams.header = action_mode == MODE_ADD && GET_FLAG('n'); + editparams.move_first = + action_mode == MODE_VERTEX_MOVE && GET_FLAG('1'); + editparams.extend_parallel = + (action_mode == MODE_EXTEND || action_mode == MODE_EXTEND_START || + action_mode == MODE_EXTEND_END) && + GET_FLAG('p'); + + List = Vect_new_list(); + + selparams->reverse = GET_FLAG('r'); + if (action_mode == MODE_COPY && BgMap && BgMap[0]) + List = select_lines(BgMap[0], selparams->bglayer, action_mode, + selparams, List); + else if (action_mode != MODE_ADD) + List = select_lines(Map, selparams->layer, action_mode, selparams, + List); + + if (action_mode != MODE_ADD && action_mode != MODE_SELECT && + List->n_values < 1) + G_warning(_("No features selected, nothing to edit")); + else { + if (action_mode == MODE_ADD) { + if (!input) + G_fatal_error(_("'%s' tool must have '%s' column"), "add", + "input"); + + if (!(editparams.input = fopen(input, "r"))) + G_fatal_error(_("Unable to open file <%s>"), input); + } + + ret = edit(Map, selparams->layer, BgMap, nbgmaps, List, action_mode, + &editparams, line); + + if (action_mode == MODE_ADD && editparams.input) + fclose(editparams.input); + } + + Vect_destroy_list(List); + + if (action_mode != MODE_SELECT && ret > 0) { + Vect_build_partial(Map, GV_BUILD_NONE); + Vect_build(Map); + total_ret += ret; + } + + G_message(" "); + } + + if (fp != stdin) + fclose(fp); + + G_message(n_("%d feature edited", "%d features edited", total_ret), + total_ret); + + return total_ret; +} + +/** + \brief Read a column from pcol string based on + https://www.rfc-editor.org/rfc/rfc4180; Does not support multi-line columns + + \param[in] pcol pointer to the start of a new column; this buffer is modified + \param[in] sep separater character + \param[out] pnext pointer to the next column or NULL if last column + + \return pointer to column + \return NULL if illegal column +*/ +static char *read_column(char *pcol, char sep, char **pnext) +{ + if (*pcol == '"') { + char *p = ++pcol; + + while ((*pnext = strchr(p, '"')) && *(*pnext + 1) == '"') + p = *pnext + 2; + + if (*pnext) { + char s = *(*pnext + 1); + + if (s == sep || !s) { + /* remove closing quote */ + **pnext = 0; + if (s == sep) + /* skip to next column */ + *pnext += 2; + else + /* last column */ + *pnext = NULL; + } + else + /* extra characters after last column; illegal column */ + pcol = NULL; + + /* convert "" to " */ + if ((p = pcol)) { + while (*p) { + if (*p == '"') { + char *q; + + for (q = p; *(q + 1); q++) + *q = *(q + 1); + *q = 0; + } + p++; + } + } + } + else + /* closing quote is missing; possibly unsupported multi-line column; + * treat it as illegal */ + pcol = NULL; + } + else if ((*pnext = strchr(pcol, sep))) + *(*pnext)++ = 0; + /* else last column */ + + return pcol; +} diff --git a/vector/v.edit/close.c b/vector/v.edit/close.c index 25b682ecbdc..8d432767877 100644 --- a/vector/v.edit/close.c +++ b/vector/v.edit/close.c @@ -1,4 +1,3 @@ -#include #include "global.h" /*! \brief Close lines (boundaries) diff --git a/vector/v.edit/edit.c b/vector/v.edit/edit.c new file mode 100644 index 00000000000..cb78c4a8236 --- /dev/null +++ b/vector/v.edit/edit.c @@ -0,0 +1,267 @@ +#include "global.h" + +static int get_snap(char *, double *thresh); + +int edit(struct Map_info *Map, int layer, struct Map_info **BgMap, int nbgmaps, + struct ilist *List, enum mode action_mode, + struct EditParams *editparams, int line) +{ + double *thresh = editparams->thresh; + struct cat_list *Clist = NULL; + struct line_pnts *coord = NULL; + int ret = 0; + + if (action_mode == MODE_CATADD || action_mode == MODE_CATDEL) { + Clist = Vect_new_cat_list(); + if (editparams->cats && Vect_str_to_cat_list(editparams->cats, Clist)) + G_fatal_error(_("Unable to get category list <%s>"), + editparams->cats); + } + + if ((action_mode == MODE_BREAK || action_mode == MODE_VERTEX_ADD || + action_mode == MODE_VERTEX_DELETE || + action_mode == MODE_VERTEX_MOVE) && + editparams->coords) { + coord = Vect_new_line_struct(); + str_to_coordinates(editparams->coords, coord); + } + + switch (action_mode) { + case MODE_ADD: { + int num_lines; + int snap_mode = get_snap(editparams->snap, thresh); + + if (!editparams->header) + Vect_read_ascii_head(editparams->input, Map); + + num_lines = Vect_get_num_lines(Map); + ret = Vect_read_ascii(editparams->input, Map); + + if (ret > 0) { + int iline; + struct ilist *List_added; + + G_message(n_("%d feature added", "%d features added", ret), ret); + + List_added = Vect_new_list(); + for (iline = num_lines + 1; iline <= Vect_get_num_lines(Map); + iline++) + Vect_list_append(List_added, iline); + + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + if (snap_mode != NO_SNAP) { /* apply snapping */ + /* snap to vertex ? */ + Vedit_snap_lines(Map, BgMap, nbgmaps, List_added, + thresh[THRESH_SNAP], + snap_mode == SNAP ? FALSE : TRUE); + } + if (editparams->close) { /* close boundaries */ + int nclosed = + close_lines(Map, GV_BOUNDARY, thresh[THRESH_SNAP]); + G_message( + n_("%d boundary closed", "%d boundaries closed", nclosed), + nclosed); + } + Vect_destroy_list(List_added); + } + break; + } + case MODE_DEL: + ret = Vedit_delete_lines(Map, List); + G_message(n_("%d feature deleted", "%d features deleted", ret), ret); + break; + case MODE_COPY: + if (BgMap && BgMap[0]) { + if (nbgmaps > 1) + G_warning( + _("Multiple background maps were given. Selected " + "features will be copied only from vector map <%s>."), + Vect_get_full_name(BgMap[0])); + + ret = Vedit_copy_lines(Map, BgMap[0], List); + } + else + ret = Vedit_copy_lines(Map, NULL, List); + G_message(n_("%d feature copied", "%d features copied", ret), ret); + break; + case MODE_MOVE: { + double move_x, move_y, move_z; + + if (sscanf(editparams->move, "%lf,%lf,%lf", &move_x, &move_y, + &move_z) != 3) + G_fatal_error(_("'%s' tool must have '%s' column"), "move", "move"); + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + ret = Vedit_move_lines(Map, BgMap, nbgmaps, List, move_x, move_y, + move_z, get_snap(editparams->snap, thresh), + thresh[THRESH_SNAP]); + G_message(n_("%d feature moved", "%d features moved", ret), ret); + break; + } + case MODE_FLIP: + ret = Vedit_flip_lines(Map, List); + G_message(n_("%d line flipped", "%d lines flipped", ret), ret); + break; + case MODE_CATADD: + ret = Vedit_modify_cats(Map, List, layer, 0, Clist); + G_message(n_("%d feature modified", "%d features modified", ret), ret); + break; + case MODE_CATDEL: + ret = Vedit_modify_cats(Map, List, layer, 1, Clist); + G_message(n_("%d feature modified", "%d features modified", ret), ret); + break; + case MODE_MERGE: + ret = Vedit_merge_lines(Map, List); + G_message(n_("%d line merged", "%d lines merged", ret), ret); + break; + case MODE_BREAK: + if (coord) + ret = Vedit_split_lines(Map, List, coord, thresh[THRESH_COORDS], + NULL); + else + ret = Vect_break_lines_list(Map, List, NULL, GV_LINES, NULL); + G_message(n_("%d line broken", "%d lines broken", ret), ret); + break; + case MODE_SNAP: + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + ret = snap_lines(Map, List, thresh[THRESH_SNAP]); + break; + case MODE_CONNECT: + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + ret = Vedit_connect_lines(Map, List, thresh[THRESH_SNAP]); + G_message(n_("%d line connected", "%d lines connected", ret), ret); + break; + case MODE_EXTEND: + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + ret = Vedit_extend_lines(Map, List, 0, editparams->extend_parallel, + thresh[THRESH_SNAP]); + G_message(n_("%d line extended", "%d lines extended", ret), ret); + break; + case MODE_EXTEND_START: + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + ret = Vedit_extend_lines(Map, List, 1, editparams->extend_parallel, + thresh[THRESH_SNAP]); + G_message(n_("%d line extended", "%d lines extended", ret), ret); + break; + case MODE_EXTEND_END: + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + ret = Vedit_extend_lines(Map, List, 2, editparams->extend_parallel, + thresh[THRESH_SNAP]); + G_message(n_("%d line extended", "%d lines extended", ret), ret); + break; + case MODE_CHTYPE: + if ((ret = Vedit_chtype_lines(Map, List)) > 0) + G_message(n_("%d feature converted", "%d features converted", ret), + ret); + else + G_message(_("No feature modified")); + break; + case MODE_VERTEX_ADD: + ret = Vedit_add_vertex(Map, List, coord, thresh[THRESH_COORDS]); + G_message(n_("%d vertex added", "%d vertices added", ret), ret); + + break; + case MODE_VERTEX_DELETE: + ret = Vedit_remove_vertex(Map, List, coord, thresh[THRESH_COORDS]); + G_message(n_("%d vertex removed", "%d vertices removed", ret), ret); + break; + case MODE_VERTEX_MOVE: { + double move_x, move_y, move_z; + + if (sscanf(editparams->move, "%lf,%lf,%lf", &move_x, &move_y, + &move_z) != 3) + G_fatal_error(_("'%s' tool must have '%s' column"), "vertexmove", + "move"); + G_verbose_message(_("Threshold value for snapping is %.2f"), + thresh[THRESH_SNAP]); + ret = Vedit_move_vertex(Map, BgMap, nbgmaps, List, coord, + thresh[THRESH_COORDS], thresh[THRESH_SNAP], + move_x, move_y, move_z, editparams->move_first, + get_snap(editparams->snap, thresh)); + G_message(n_("%d vertex moved", "%d vertices moved", ret), ret); + break; + } + case MODE_AREA_DEL: { + int i; + for (i = 0; i < List->n_values; i++) { + if (Vect_get_line_type(Map, List->value[i]) != GV_CENTROID) { + G_warning(_("Select feature %d is not centroid, ignoring..."), + List->value[i]); + continue; + } + + ret += Vedit_delete_area_centroid(Map, List->value[i]); + } + G_message(n_("%d area removed", "%d areas removed", ret), ret); + break; + } + case MODE_ZBULK: { + double start, step; + double x1, y1, x2, y2; + + /* in batch editing (line > 0), check if Map is 3D; in non-batch + * editing (line == 0), this check is done earlier in main.c */ + if (line && !Vect_is_3d(Map)) { + Vect_close(Map); + G_fatal_error(_("Vector map <%s> is not 3D. Tool '%s' requires " + "3D vector map. Please convert the vector map " + "to 3D using e.g. %s."), + Map->name, "zbulk", "v.extrude"); + } + + if (sscanf(editparams->zbulk, "%lf,%lf", &start, &step) != 2) + G_fatal_error(_("'%s' tool must have '%s' column"), "zbulk", + "zbulk"); + + if (!editparams->bbox || sscanf(editparams->bbox, "%lf,%lf,%lf,%lf", + &x1, &y1, &x2, &y2) != 4) + G_fatal_error(_("ZBulk must have bbox")); + + ret = Vedit_bulk_labeling(Map, List, x1, y1, x2, y2, start, step); + G_message(n_("%d line labeled", "%d lines labeled", ret), ret); + break; + } + case MODE_SELECT: + ret = print_selected(List); + break; + case MODE_CREATE: + case MODE_NONE: + case MODE_BATCH: + break; + } + + if (Clist) + Vect_destroy_cat_list(Clist); + + if (coord) + Vect_destroy_line_struct(coord); + + return ret; +} + +static int get_snap(char *snap, double *thresh) +{ + int snap_mode = NO_SNAP; + + if (snap) { + if (strcmp(snap, "node") == 0) + snap_mode = SNAP; + else if (strcmp(snap, "vertex") == 0) + snap_mode = SNAPVERTEX; + else if (strcmp(snap, "no") != 0) + G_fatal_error(_("Unsupported snap '%s'"), snap); + if (snap_mode != NO_SNAP && thresh[THRESH_SNAP] <= 0) { + G_warning( + _("Threshold for snapping must be > 0. No snapping applied.")); + snap_mode = NO_SNAP; + } + } + + return snap_mode; +} diff --git a/vector/v.edit/global.h b/vector/v.edit/global.h index 745bd03fd1a..a89049f858d 100644 --- a/vector/v.edit/global.h +++ b/vector/v.edit/global.h @@ -46,14 +46,28 @@ enum mode { /* change feature type (point<->centroid, line<->boundary) */ MODE_CHTYPE, MODE_AREA_DEL, /* delete area */ + MODE_BATCH, /* batch editing */ }; struct GParams { struct Option *map, *in, *maxdist, *tool, *coord, *cat, *move, *bbox, *fld, - *poly, *type, *id, *where, *bmaps, *snap, *query, *zbulk; + *poly, *type, *id, *where, *bmaps, *snap, *query, *zbulk, *batch, *sep; struct Flag *header, *topo, *close, *reverse, *move_first, *extend_parallel; }; +struct SelectParams { + int layer, bglayer, type, reverse; + char *ids, *cats, *coords, *bbox, *polygon, *where, *query; + double *thresh; +}; + +struct EditParams { + FILE *input; + char *move, *cats, *coords, *snap, *zbulk, *bbox; + double *thresh; + int close, header, move_first, extend_parallel; +}; + #include "proto.h" #endif diff --git a/vector/v.edit/main.c b/vector/v.edit/main.c index c49b99138cc..8355c5da11a 100644 --- a/vector/v.edit/main.c +++ b/vector/v.edit/main.c @@ -7,9 +7,9 @@ * AUTHOR(S): Wolf Bergenheim * Jachym Cepicky * Major updates by Martin Landa - * Extend tools by Huidae Cho + * Extend tools and batch editing by Huidae Cho * - * COPYRIGHT: (C) 2006-2017 by the GRASS Development Team + * COPYRIGHT: (C) 2006-2024 by the GRASS Development Team * * This program is free software under the GNU General * Public License (>=v2). Read the file COPYING that comes @@ -20,6 +20,8 @@ #include "global.h" +static void error_handler(void *); + int main(int argc, char *argv[]) { struct GModule *module; @@ -29,24 +31,15 @@ int main(int argc, char *argv[]) int nbgmaps; /* number of registrated background maps */ enum mode action_mode; FILE *ascii; - + struct SelectParams selparams; + struct EditParams editparams; int i; - int move_first, snap, extend_parallel; - int ret, layer; - double move_x, move_y, move_z, thresh[3]; - - struct line_pnts *coord; - - struct ilist *List; - - struct cat_list *Clist; + int ret; + double thresh[3]; ascii = NULL; - List = NULL; BgMap = NULL; nbgmaps = 0; - coord = NULL; - Clist = NULL; G_gisinit(argv[0]); @@ -67,13 +60,6 @@ int main(int argc, char *argv[]) if (!parser(argc, argv, ¶ms, &action_mode)) exit(EXIT_FAILURE); - /* get list of categories */ - Clist = Vect_new_cat_list(); - if (params.cat->answer && Vect_str_to_cat_list(params.cat->answer, Clist)) { - G_fatal_error(_("Unable to get category list <%s>"), - params.cat->answer); - } - /* open input file */ if (params.in->answer) { if (strcmp(params.in->answer, "-") != 0) { @@ -81,9 +67,8 @@ int main(int argc, char *argv[]) if (ascii == NULL) G_fatal_error(_("Unable to open file <%s>"), params.in->answer); } - else { + else ascii = stdin; - } } if (!ascii && action_mode == MODE_ADD) G_fatal_error(_("Required parameter <%s> not set"), params.in->key); @@ -103,10 +88,9 @@ int main(int argc, char *argv[]) /* 3D vector maps? */ putenv("GRASS_VECTOR_EXTERNAL_IMMEDIATE=1"); ret = Vect_open_new(&Map, params.map->answer, WITHOUT_Z); - if (ret == -1) { + if (ret == -1) G_fatal_error(_("Unable to create vector map <%s>"), params.map->answer); - } Vect_set_error_handler_io(NULL, &Map); /* native or external data source ? */ @@ -127,10 +111,8 @@ int main(int argc, char *argv[]) G_debug(1, "Map created"); - if (ascii) { - /* also add new vector features */ + if (ascii) /* also add new vector features */ action_mode = MODE_ADD; - } } else { /* open selected vector file */ if (action_mode == MODE_ADD) /* write */ @@ -179,7 +161,6 @@ int main(int argc, char *argv[]) } } - layer = Vect_get_field_number(&Map, params.fld->answer); i = 0; while (params.maxdist->answers[i]) { switch (i) { @@ -201,285 +182,122 @@ int main(int argc, char *argv[]) i++; } - move_first = params.move_first->answer ? 1 : 0; - extend_parallel = params.extend_parallel->answer ? 1 : 0; - snap = NO_SNAP; - if (strcmp(params.snap->answer, "node") == 0) - snap = SNAP; - else if (strcmp(params.snap->answer, "vertex") == 0) - snap = SNAPVERTEX; - if (snap != NO_SNAP && thresh[THRESH_SNAP] <= 0) { - G_warning( - _("Threshold for snapping must be > 0. No snapping applied.")); - snap = NO_SNAP; - } + if (action_mode == MODE_BATCH) { + char *sep = G_option_to_separator(params.sep); - if (action_mode != MODE_CREATE && action_mode != MODE_ADD) { - /* select lines */ - List = Vect_new_list(); - G_message(_("Selecting features...")); - if (action_mode == MODE_COPY && BgMap && BgMap[0]) { - List = select_lines(BgMap[0], action_mode, ¶ms, thresh, List); - } - else { - List = select_lines(&Map, action_mode, ¶ms, thresh, List); - } - } + if (*(sep + 1)) + G_fatal_error(_("Field separator must be a single character")); + else if (*sep == '\n') + G_fatal_error(_("Field separator cannot be a newline")); - if ((action_mode != MODE_CREATE && action_mode != MODE_ADD && - action_mode != MODE_SELECT)) { - if (List->n_values < 1) { - G_warning(_("No features selected, nothing to edit")); - action_mode = MODE_NONE; - ret = 0; - } - else { - /* reopen the map for updating */ - if (action_mode == MODE_ZBULK && !Vect_is_3d(&Map)) { - Vect_close(&Map); - G_fatal_error(_("Vector map <%s> is not 3D. Tool '%s' requires " - "3D vector map. " - "Please convert the vector map " - "to 3D using e.g. %s."), - params.map->answer, params.tool->answer, - "v.extrude"); - } - Vect_close(&Map); + if (Vect_open_update2(&Map, params.map->answer, G_mapset(), + params.fld->answer) < 0) + G_fatal_error(_("Unable to open vector map <%s>"), + params.map->answer); + G_add_error_handler(error_handler, &Map); - if (Vect_open_update2(&Map, params.map->answer, G_mapset(), - params.fld->answer) < 0) - G_fatal_error(_("Unable to open vector map <%s>"), - params.map->answer); - } - } + selparams.layer = Vect_get_field_number(&Map, params.fld->answer); + if (BgMap && BgMap[0]) + selparams.bglayer = + Vect_get_field_number(BgMap[0], params.fld->answer); + selparams.type = Vect_option_to_types(params.type); + selparams.thresh = thresh; - /* coords option -> array */ - if (params.coord->answers) { - coord = Vect_new_line_struct(); - int i = 0; - double east, north; - - while (params.coord->answers[i]) { - east = atof(params.coord->answers[i]); - north = atof(params.coord->answers[i + 1]); - Vect_append_point(coord, east, north, 0.0); - i += 2; - } + batch_edit(&Map, BgMap, nbgmaps, params.batch->answer, *sep, + &selparams); } + else { + struct ilist *List = NULL; + + if (action_mode != MODE_CREATE && action_mode != MODE_ADD) { + /* select lines */ + if (action_mode == MODE_COPY && BgMap && BgMap[0]) + selparams.bglayer = + Vect_get_field_number(BgMap[0], params.fld->answer); + else + selparams.layer = + Vect_get_field_number(&Map, params.fld->answer); + selparams.type = Vect_option_to_types(params.type); + selparams.reverse = params.reverse->answer; + selparams.ids = params.id->answer; + selparams.cats = params.cat->answer; + selparams.coords = params.coord->answer; + selparams.bbox = params.bbox->answer; + selparams.polygon = params.poly->answer; + selparams.where = params.where->answer; + selparams.query = params.query->answer; + selparams.thresh = thresh; + + List = Vect_new_list(); + if (action_mode == MODE_COPY && BgMap && BgMap[0]) + List = select_lines(BgMap[0], selparams.bglayer, action_mode, + &selparams, List); + else + List = select_lines(&Map, selparams.layer, action_mode, + &selparams, List); + } - /* perform requested editation */ - switch (action_mode) { - case MODE_CREATE: - break; - case MODE_ADD: - if (!params.header->answer) - Vect_read_ascii_head(ascii, &Map); - int num_lines; - - num_lines = Vect_get_num_lines(&Map); - - ret = Vect_read_ascii(ascii, &Map); - if (ret > 0) { - int iline; - struct ilist *List_added; - - G_message(n_("%d feature added", "%d features added", ret), ret); - - List_added = Vect_new_list(); - for (iline = num_lines + 1; iline <= Vect_get_num_lines(&Map); - iline++) - Vect_list_append(List_added, iline); - - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - if (snap != NO_SNAP) { /* apply snapping */ - /* snap to vertex ? */ - Vedit_snap_lines(&Map, BgMap, nbgmaps, List_added, - thresh[THRESH_SNAP], - snap == SNAP ? FALSE : TRUE); + if ((action_mode != MODE_CREATE && action_mode != MODE_ADD && + action_mode != MODE_SELECT)) { + if (List->n_values < 1) { + G_warning(_("No features selected, nothing to edit")); + action_mode = MODE_NONE; + ret = 0; } - if (params.close->answer) { /* close boundaries */ - int nclosed; + else { + /* reopen the map for updating */ + if (action_mode == MODE_ZBULK && !Vect_is_3d(&Map)) { + Vect_close(&Map); + G_fatal_error( + _("Vector map <%s> is not 3D. Tool '%s' requires " + "3D vector map. " + "Please convert the vector map " + "to 3D using e.g. %s."), + params.map->answer, params.tool->answer, "v.extrude"); + } + Vect_close(&Map); - nclosed = close_lines(&Map, GV_BOUNDARY, thresh[THRESH_SNAP]); - G_message( - n_("%d boundary closed", "%d boundaries closed", nclosed), - nclosed); + if (Vect_open_update2(&Map, params.map->answer, G_mapset(), + params.fld->answer) < 0) + G_fatal_error(_("Unable to open vector map <%s>"), + params.map->answer); } - Vect_destroy_list(List_added); - } - break; - case MODE_DEL: - ret = Vedit_delete_lines(&Map, List); - G_message(n_("%d feature deleted", "%d features deleted", ret), ret); - break; - case MODE_MOVE: - move_x = atof(params.move->answers[0]); - move_y = atof(params.move->answers[1]); - move_z = atof(params.move->answers[2]); - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - ret = Vedit_move_lines(&Map, BgMap, nbgmaps, List, move_x, move_y, - move_z, snap, thresh[THRESH_SNAP]); - G_message(n_("%d feature moved", "%d features moved", ret), ret); - break; - case MODE_VERTEX_MOVE: - move_x = atof(params.move->answers[0]); - move_y = atof(params.move->answers[1]); - move_z = atof(params.move->answers[2]); - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - ret = Vedit_move_vertex(&Map, BgMap, nbgmaps, List, coord, - thresh[THRESH_COORDS], thresh[THRESH_SNAP], - move_x, move_y, move_z, move_first, snap); - G_message(n_("%d vertex moved", "%d vertices moved", ret), ret); - break; - case MODE_VERTEX_ADD: - ret = Vedit_add_vertex(&Map, List, coord, thresh[THRESH_COORDS]); - G_message(n_("%d vertex added", "%d vertices added", ret), ret); - break; - case MODE_VERTEX_DELETE: - ret = Vedit_remove_vertex(&Map, List, coord, thresh[THRESH_COORDS]); - G_message(n_("%d vertex removed", "%d vertices removed", ret), ret); - break; - case MODE_BREAK: - if (params.coord->answer) { - ret = Vedit_split_lines(&Map, List, coord, thresh[THRESH_COORDS], - NULL); - } - else { - ret = Vect_break_lines_list(&Map, List, NULL, GV_LINES, NULL); - } - G_message(n_("%d line broken", "%d lines broken", ret), ret); - break; - case MODE_CONNECT: - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - ret = Vedit_connect_lines(&Map, List, thresh[THRESH_SNAP]); - G_message(n_("%d line connected", "%d lines connected", ret), ret); - break; - case MODE_EXTEND: - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - ret = Vedit_extend_lines(&Map, List, 0, extend_parallel, - thresh[THRESH_SNAP]); - G_message(n_("%d line extended", "%d lines extended", ret), ret); - break; - case MODE_EXTEND_START: - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - ret = Vedit_extend_lines(&Map, List, 1, extend_parallel, - thresh[THRESH_SNAP]); - G_message(n_("%d line extended", "%d lines extended", ret), ret); - break; - case MODE_EXTEND_END: - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - ret = Vedit_extend_lines(&Map, List, 2, extend_parallel, - thresh[THRESH_SNAP]); - G_message(n_("%d line extended", "%d lines extended", ret), ret); - break; - case MODE_MERGE: - ret = Vedit_merge_lines(&Map, List); - G_message(n_("%d line merged", "%d lines merged", ret), ret); - break; - case MODE_SELECT: - ret = print_selected(List); - break; - case MODE_CATADD: - ret = Vedit_modify_cats(&Map, List, layer, 0, Clist); - G_message(n_("%d feature modified", "%d features modified", ret), ret); - break; - case MODE_CATDEL: - ret = Vedit_modify_cats(&Map, List, layer, 1, Clist); - G_message(n_("%d feature modified", "%d features modified", ret), ret); - break; - case MODE_COPY: - if (BgMap && BgMap[0]) { - if (nbgmaps > 1) - G_warning(_("Multiple background maps were given. " - "Selected features will be copied only from " - "vector map <%s>."), - Vect_get_full_name(BgMap[0])); - - ret = Vedit_copy_lines(&Map, BgMap[0], List); - } - else { - ret = Vedit_copy_lines(&Map, NULL, List); } - G_message(n_("%d feature copied", "%d features copied", ret), ret); - break; - case MODE_SNAP: - G_verbose_message(_("Threshold value for snapping is %.2f"), - thresh[THRESH_SNAP]); - ret = snap_lines(&Map, List, thresh[THRESH_SNAP]); - break; - case MODE_FLIP: - ret = Vedit_flip_lines(&Map, List); - G_message(n_("%d line flipped", "%d lines flipped", ret), ret); - break; - case MODE_NONE: - break; - case MODE_ZBULK: { - double start, step; - double x1, y1, x2, y2; - - start = atof(params.zbulk->answers[0]); - step = atof(params.zbulk->answers[1]); - - x1 = atof(params.bbox->answers[0]); - y1 = atof(params.bbox->answers[1]); - x2 = atof(params.bbox->answers[2]); - y2 = atof(params.bbox->answers[3]); - - ret = Vedit_bulk_labeling(&Map, List, x1, y1, x2, y2, start, step); - - G_message(n_("%d line labeled", "%d lines labeled", ret), ret); - break; - } - case MODE_CHTYPE: - ret = Vedit_chtype_lines(&Map, List); - if (ret > 0) { - G_message(n_("%d feature converted", "%d features converted", ret), - ret); - } - else { - G_message(_("No feature modified")); - } - break; - case MODE_AREA_DEL: { - ret = 0; - for (i = 0; i < List->n_values; i++) { - if (Vect_get_line_type(&Map, List->value[i]) != GV_CENTROID) { - G_warning(_("Select feature %d is not centroid, ignoring..."), - List->value[i]); - continue; + if (action_mode != MODE_NONE) { + editparams.input = ascii; + editparams.move = params.move->answer; + editparams.cats = params.cat->answer; + editparams.coords = params.coord->answer; + editparams.snap = params.snap->answer; + editparams.zbulk = params.zbulk->answer; + editparams.bbox = params.bbox->answer; + editparams.thresh = thresh; + editparams.close = params.close->answer ? 1 : 0; + editparams.header = params.header->answer ? 1 : 0; + editparams.move_first = params.move_first->answer ? 1 : 0; + editparams.extend_parallel = params.extend_parallel->answer ? 1 : 0; + + /* perform requested editation */ + ret = edit(&Map, selparams.layer, BgMap, nbgmaps, List, action_mode, + &editparams, 0); + + /* build topology only if requested or if tool!=select */ + if (action_mode != MODE_SELECT && ret > 0 && + params.topo->answer != 1) { + Vect_build_partial(&Map, GV_BUILD_NONE); + Vect_build(&Map); } - - ret += Vedit_delete_area_centroid(&Map, List->value[i]); } - G_message(n_("%d area removed", "%d areas removed", ret), ret); - break; - } - default: - G_warning(_("Operation not implemented")); - ret = -1; - break; - } - Vect_hist_command(&Map); - - /* build topology only if requested or if tool!=select */ - if (action_mode != MODE_SELECT && action_mode != MODE_NONE && - params.topo->answer != 1) { - Vect_build_partial(&Map, GV_BUILD_NONE); - Vect_build(&Map); + if (List) + Vect_destroy_list(List); } - if (List) - Vect_destroy_list(List); + if (ascii && ascii != stdout) + fclose(ascii); + Vect_hist_command(&Map); Vect_close(&Map); G_debug(1, "Map closed"); @@ -491,18 +309,16 @@ int main(int argc, char *argv[]) } G_free((void *)BgMap); - if (coord) - Vect_destroy_line_struct(coord); + G_done_msg(" "); - if (Clist) - Vect_destroy_cat_list(Clist); + exit(ret > -1 ? EXIT_SUCCESS : EXIT_FAILURE); +} - G_done_msg(" "); +static void error_handler(void *p) +{ + struct Map_info *Map = (struct Map_info *)p; - if (ret > -1) { - exit(EXIT_SUCCESS); - } - else { - exit(EXIT_FAILURE); - } + Vect_build_partial(Map, GV_BUILD_NONE); + Vect_build(Map); + Vect_close(Map); } diff --git a/vector/v.edit/proto.h b/vector/v.edit/proto.h index b8d0226e60d..4e0d0e35d66 100644 --- a/vector/v.edit/proto.h +++ b/vector/v.edit/proto.h @@ -9,8 +9,8 @@ int close_lines(struct Map_info *, int, double); /* select.c */ int print_selected(struct ilist *); -struct ilist *select_lines(struct Map_info *, enum mode, struct GParams *, - double *, struct ilist *); +struct ilist *select_lines(struct Map_info *, int, enum mode, + struct SelectParams *, struct ilist *); int sel_by_cat(struct Map_info *, struct cat_list *, int, int, char *, struct ilist *); int sel_by_coordinates(struct Map_info *, int, struct line_pnts *, double, @@ -23,6 +23,9 @@ int sel_by_where(struct Map_info *, int, int, char *, struct ilist *); int reverse_selection(struct Map_info *, int, struct ilist **); int sel_by_query(struct Map_info *, int, int, double, const char *, struct ilist *); +int str_to_coordinates(const char *, struct line_pnts *); +int str_to_bbox(const char *, struct line_pnts *); +int str_to_polygon(const char *, struct line_pnts *); /* snap.c */ int snap_lines(struct Map_info *, struct ilist *, double); @@ -32,4 +35,12 @@ int snap_line(struct Map_info *, int, int, double); double max_distance(double); void coord2bbox(double, double, double, struct line_pnts *); +/* batch.c */ +int batch_edit(struct Map_info *, struct Map_info **, int, const char *, char, + struct SelectParams *); + +/* edit.c */ +int edit(struct Map_info *, int, struct Map_info **, int, struct ilist *, + enum mode, struct EditParams *, int); + #endif /* _V_EDIT_PROTO */ diff --git a/vector/v.edit/select.c b/vector/v.edit/select.c index ec5ec1db8b0..4cafbff12b5 100644 --- a/vector/v.edit/select.c +++ b/vector/v.edit/select.c @@ -7,7 +7,7 @@ * AUTHOR(S): GRASS Development Team * Wolf Bergenheim, Jachym Cepicky, Martin Landa * - * COPYRIGHT: (C) 2006-2008 by the GRASS Development Team + * COPYRIGHT: (C) 2006-2024 by the GRASS Development Team * * This program is free software under the * GNU General Public License (>=v2). @@ -20,7 +20,7 @@ #include #include "global.h" -static char first_selection = 1; +static char first_selection; static int merge_lists(struct ilist *, struct ilist *); static int merge_lists2(struct ilist *, struct boxlist *); @@ -28,46 +28,39 @@ static int merge_lists2(struct ilist *, struct boxlist *); \brief Select vector features \param[in] Map vector map + \param[in] layer map layer \param[in] action_mode tool - \param[in] params GRASS parameters + \param[in] selparams select parameters \param[in] List list of selected features \return list of newly selected features */ -struct ilist *select_lines(struct Map_info *Map, enum mode action_mode, - struct GParams *params, double *thresh, - struct ilist *List) +struct ilist *select_lines(struct Map_info *Map, int layer, + enum mode action_mode, + struct SelectParams *selparams, struct ilist *List) { - int layer, type; + int type = selparams->type; + double *thresh = selparams->thresh; - layer = Vect_get_field_number(Map, params->fld->answer); - type = Vect_option_to_types(params->type); + G_message(_("Selecting features...")); + + first_selection = 1; /* select by id's */ - if (params->id->answer != NULL) { - sel_by_id(Map, type, params->id->answer, List); - } + if (selparams->ids) + sel_by_id(Map, type, selparams->ids, List); /* select by category (ignore tools catdel and catadd) */ - if ((action_mode != MODE_CATADD && action_mode != MODE_CATDEL) && - params->cat->answer != NULL) { - sel_by_cat(Map, NULL, layer, type, params->cat->answer, List); - } + if (action_mode != MODE_CATADD && action_mode != MODE_CATDEL && + selparams->cats) + sel_by_cat(Map, NULL, layer, type, selparams->cats, List); /* select by coordinates (+threshold) */ - if (params->coord->answer != NULL) { - int i; - double east, north; + if (selparams->coords) { struct line_pnts *coords; coords = Vect_new_line_struct(); - i = 0; - while (params->coord->answers[i]) { - east = atof(params->coord->answers[i]); - north = atof(params->coord->answers[i + 1]); - Vect_append_point(coords, east, north, 0.0); - i += 2; - } + str_to_coordinates(selparams->coords, coords); G_verbose_message(_("Threshold value for coordinates is %.2f"), thresh[THRESH_COORDS]); @@ -77,22 +70,11 @@ struct ilist *select_lines(struct Map_info *Map, enum mode action_mode, } /* select by bbox */ - if (params->bbox->answer != NULL) { + if (selparams->bbox) { struct line_pnts *bbox; - double x1, y1, x2, y2; bbox = Vect_new_line_struct(); - - x1 = atof(params->bbox->answers[0]); - y1 = atof(params->bbox->answers[1]); - x2 = atof(params->bbox->answers[2]); - y2 = atof(params->bbox->answers[3]); - - Vect_append_point(bbox, x1, y1, -PORT_DOUBLE_MAX); - Vect_append_point(bbox, x2, y1, PORT_DOUBLE_MAX); - Vect_append_point(bbox, x2, y2, -PORT_DOUBLE_MAX); - Vect_append_point(bbox, x1, y2, PORT_DOUBLE_MAX); - Vect_append_point(bbox, x1, y1, -PORT_DOUBLE_MAX); + str_to_bbox(selparams->bbox, bbox); /* sel_by_bbox not used */ /* @@ -106,23 +88,11 @@ struct ilist *select_lines(struct Map_info *Map, enum mode action_mode, } /* select by polygon */ - if (params->poly->answer != NULL) { - int i; + if (selparams->polygon) { struct line_pnts *Polygon; Polygon = Vect_new_line_struct(); - - for (i = 0; params->poly->answers[i]; i += 2) { - Vect_append_point(Polygon, atof(params->poly->answers[i]), - atof(params->poly->answers[i + 1]), 0.0); - } - - /* if first and last point of polygon does not match */ - if (atof(params->poly->answers[i - 1]) != - atof(params->poly->answers[0])) { - Vect_append_point(Polygon, atof(params->poly->answers[0]), - atof(params->poly->answers[1]), 0.0); - } + str_to_polygon(selparams->polygon, Polygon); sel_by_polygon(Map, type, Polygon, List); @@ -130,12 +100,11 @@ struct ilist *select_lines(struct Map_info *Map, enum mode action_mode, } /* select by where statement */ - if (params->where->answer != NULL) { - sel_by_where(Map, layer, type, params->where->answer, List); - } + if (selparams->where) + sel_by_where(Map, layer, type, selparams->where, List); /* selecy by query */ - if (params->query->answer != NULL) { + if (selparams->query) { int query_type; struct ilist *List_tmp; @@ -143,17 +112,14 @@ struct ilist *select_lines(struct Map_info *Map, enum mode action_mode, List_tmp = List; first_selection = 0; } - else { + else List_tmp = Vect_new_list(); - } query_type = QUERY_UNKNOWN; - if (strcmp(params->query->answer, "length") == 0) { + if (strcmp(selparams->query, "length") == 0) query_type = QUERY_LENGTH; - } - else if (strcmp(params->query->answer, "dangle") == 0) { + else if (strcmp(selparams->query, "dangle") == 0) query_type = QUERY_DANGLE; - } G_verbose_message(_("Threshold value for querying is %.2f"), thresh[THRESH_QUERY]); @@ -167,9 +133,8 @@ struct ilist *select_lines(struct Map_info *Map, enum mode action_mode, } } - if (params->reverse->answer) { + if (selparams->reverse) reverse_selection(Map, type, &List); - } G_message(n_("%d of %d feature selected from vector map <%s>", "%d of %d features selected from vector map <%s>", @@ -221,7 +186,6 @@ int sel_by_cat(struct Map_info *Map, struct cat_list *cl_orig, int layer, { struct ilist *List_tmp, *List_tmp1; struct cat_list *cl; - int i, cat; if (first_selection || cl_orig) { @@ -624,3 +588,85 @@ int reverse_selection(struct Map_info *Map, int type, struct ilist **List) return 1; } + +/** + \brief Convert string of list of coordinates to line_pnts + + \param[in] str list of coordinates as string + \param[in,out] coords pointer to line_pnts structure + + \return number of parsed points +*/ +int str_to_coordinates(const char *str, struct line_pnts *coords) +{ + char *p = (char *)str, *psep; + int npoints = 0, read_east = 1; + double east, north; + + while ((psep = strchr(p, ',')) || (psep = strchr(p, 0))) { + if (read_east) + sscanf(p, "%lf", &east); + else { + sscanf(p, "%lf", &north); + Vect_append_point(coords, east, north, 0.0); + npoints++; + } + + read_east = !read_east; + if (!*psep) + break; + p = psep + 1; + } + + if (!read_east) + G_fatal_error(_("Coordinates must be provided in multiples of %d"), 2); + + return npoints; +} + +/** + \brief Convert string of list of coordinates to bbox + + \param[in] str list of coordinates as string + \param[in,out] bbox pointer to line_pnts structure + + \return number of parsed points +*/ +int str_to_bbox(const char *str, struct line_pnts *bbox) +{ + double x1, y1, x2, y2; + + if (sscanf(str, "%lf,%lf,%lf,%lf", &x1, &y1, &x2, &y2) != 4) + G_fatal_error(_("Bounding box must have 2 coordinate pairs")); + + Vect_append_point(bbox, x1, y1, -PORT_DOUBLE_MAX); + Vect_append_point(bbox, x2, y1, PORT_DOUBLE_MAX); + Vect_append_point(bbox, x2, y2, -PORT_DOUBLE_MAX); + Vect_append_point(bbox, x1, y2, PORT_DOUBLE_MAX); + Vect_append_point(bbox, x1, y1, -PORT_DOUBLE_MAX); + + return 2; +} + +/** + \brief Convert string of list of coordinates to polygon + + \param[in] str list of coordinates as string + \param[in,out] polygon pointer to line_pnts structure + + \return number of parsed points +*/ +int str_to_polygon(const char *str, struct line_pnts *Polygon) +{ + int npoints; + + if ((npoints = str_to_coordinates(str, Polygon)) < 3) + G_fatal_error(_("Polygon must have at least 3 coordinate pairs")); + + /* if first and last point of polygon does not match */ + if (Polygon->x[Polygon->n_points - 1] != Polygon->x[0] || + Polygon->y[Polygon->n_points - 1] != Polygon->y[0]) + Vect_append_point(Polygon, Polygon->x[0], Polygon->y[0], 0.0); + + return npoints; +} diff --git a/vector/v.edit/v.edit.html b/vector/v.edit/v.edit.html index 081deadf29c..215036fa368 100644 --- a/vector/v.edit/v.edit.html +++ b/vector/v.edit/v.edit.html @@ -168,6 +168,70 @@

Tool description

id's. No editing is done. +

Batch editing

+ +With the batch option, v.edit supports batch editing using a +CSV-like table. Batch editing can be faster than multiple independent +v.edit commands because of reduced overheads. The batch table supports +the following column names: +
    +
  • tool (required) - Tool name from the tool option; + create tool is not supported
  • +
  • flags - a combination of all flags except -b without + dashes; order is not important; e.g., r1 and 1r have the + same effect
  • +
  • input - input option; 'stdin' is not supported
  • +
  • move - move option
  • +
  • ids - ids option
  • +
  • cats - cats option
  • +
  • coords - coords option
  • +
  • bbox - bbox option
  • +
  • polygon - polygon option
  • +
  • where - where option
  • +
  • query - query option
  • +
  • snap - snap option
  • +
  • zbulk - zbulk option
  • +
+ +The batch option is exclusive to all the above options. The only +required column is tool and the order of columns is not important. +Multiple tables with different columns can be defined in one batch file using +empty lines to separate them. Different tools can be used in one table, but all +necessary columns for all used tools in that table need to be declared even if +some tools do not use certain options. In this case, columns for these unused +options can be left blank. + +

For each line in the table, v.edit commands can be specified. For +example, these two tables when passed to the batch option with +separator=comma +

+tool,cats,coords
+break,1,"637108.11963224,257241.783755701"
+
+tool,flags,cats
+delete,r,1
+
+are equivalent to the following individual commands: +
+v.edit map=vect_map tool=break cat=1 coords=637108.11963224,257241.783755701
+v.edit -r map=vect_map tool=delete cat=1
+
+ +

The above two tables can be merged into one by declaring the union of two +column sets and leaving irrelevant columns empty: +

+tool,flags,cats,coords
+break,,1,"637108.11963224,257241.783755701"
+delete,r,1,
+
+ +

For batch editing, topology is always built whenever there are any changes +in features because successive edits require properly built topology including +feature ID changes. + +

The separator option only supports a single-character separator that +is not a newline. +

EXAMPLES

Create new vector map

@@ -181,7 +245,7 @@

Create new vector map

Create new vector map and read data from file 'roads.txt':
-v.out.ascii in=roads format=standard > roads.txt;
+v.out.ascii in=roads format=standard > roads.txt;
 v.edit tool=create map=vectmap input=roads.txt
 
@@ -430,6 +494,78 @@

Fix height of contours

zbulk=1000,10 +

Batch editing

+ +Delete lower halves of roadsmajor: + +
+# copy roadsmajor
+g.copy vect=roadsmajor,roadsmajor_upper_half
+
+# find road centers
+v.db.select map=roadsmajor -c col=cat |
+        awk '{printf "P %d %d 50%%\n", $1, $1}' |
+        v.segment roadsmajor output=roadsmajor_center
+
+# create a batch table and pipe it to v.edit
+(
+# use these batch columns
+echo "tool|cats|coords"
+
+# break roads at their center
+v.to.db -p map=roadsmajor_center option=coor sep=space |
+        awk '!/^cat/{printf "break|%d|%s,%s\n", $1, $2, $3}'
+
+# delete lower halves
+v.to.db -p map=roadsmajor option=end sep=space |
+        awk '!/^cat/{printf "delete|%d|%s,%s\n", $1, $2, $3}'
+
+# the above three commands produce the following table:
+# tool|cats|coords
+# break|1|637108.11963224,257241.783755701
+# break|2|636792.27198317,254339.365766968
+# break|3|638449.758677739,248299.357582029
+# ...
+# delete|1|637209.083058167,257970.129540226
+# delete|2|636981.336042672,256517.602235172
+# delete|3|637960.264160529,248293.868427705
+# ...
+) | v.edit map=roadsmajor_upper_half batch=-
+
+ +Red is the extracted upper halves of 'roadsmajor': + +
+Upper-half major roads +
+ +On the i9-12900 CPU, the above batch command performed the same edits in 2 +seconds compared to 12 seconds by this equivalent script: + +
+# copy roadsmajor
+g.copy vect=roadsmajor,roadsmajor_upper_half
+
+# find road centers
+v.db.select map=roadsmajor -c col=cat |
+        awk '{printf "P %d %d 50%%\n", $1, $1}' |
+        v.segment roadsmajor output=roadsmajor_center
+
+# break roads at their center
+v.to.db -p map=roadsmajor_center option=coor sep=space |
+	sed '/^cat/d' |
+	while read cat x y z; do
+	v.edit map=roadsmajor_upper_half tool=break cats=$cat coords=$x,$y
+done
+
+# delete lower halves
+v.to.db -p map=roadsmajor option=end sep=space |
+	sed '/^cat/d' |
+	while read cat x y z; do
+	v.edit map=roadsmajor_upper_half tool=delete cats=$cat coords=$x,$y
+done
+
+

SEE ALSO

@@ -449,4 +585,4 @@

AUTHORS

Original author: Wolf Bergenheim - independent developer
Initial updates: Jachym Cepicky, Mendel University of Agriculture and Forestry in Brno, Czech Republic
Major update by Martin Landa, FBK-irst (formerly ITC-irst), Trento, Italy
-Extend tools by Huidae Cho +Extend tools and batch editing by Huidae Cho, New Mexico State University diff --git a/vector/v.edit/v_edit_roadsmajor_upper_half.png b/vector/v.edit/v_edit_roadsmajor_upper_half.png new file mode 100644 index 00000000000..8009ba663f9 Binary files /dev/null and b/vector/v.edit/v_edit_roadsmajor_upper_half.png differ