/***************************************************************
 *
 * MODULE:       v.distance
 *
 * AUTHOR(S):    - J.Soimasuo 15.9.1994, University of Joensuu,
 *                 Faculty of Forestry, Finland
 *               - some additions by Markus Neteler (2002)
 *               - updated to 5.7 by Radim Blazek (2003)
 *               - OGR support by Martin Landa <landa.martin gmail.com> (2009)
 *               - speed-up for dmax != 0 by Markus Metz (2010)
 *               - support all features by Markus Metz (2012)
 *               - create a new table for non -a runs by Huidae Cho (2017)
 *
 * PURPOSE:      Calculates distance from a point to nearest feature in vector
 *               layer.
 *
 * COPYRIGHT:    (C) 2002-2017 by the GRASS Development Team
 *
 *               This program is free software under the
 *               GNU General Public License (>=v2).
 *               Read the file COPYING that comes with GRASS
 *               for details.
 *
 **************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <grass/gis.h>
#include <grass/glocale.h>
#include <grass/vector.h>
#include "local_proto.h"

/* Supported command lines:
 * from= to= upload= -p                 # print
 * from= to= upload= column=            # update the "from" table
 * from= to= output=                    # create map
 * from= to= output= upload= -p         # create map & print
 * from= to= output= upload= column=    # create map & update the "from" table
 * from= to= output= upload= column= table=
 *                                      # create map & table
 * -a from= to= upload= -p              # print for all
 * -a from= to= output=                 # create map for all
 * -a from= to= output= upload= -p      # create map for all & print
 * -a from= to= output= upload= column= table=
 *                                      # create map for all & create table
 *
 * Unsupported command lines:
 * from= to=                            # nothing to do
 * from= to= upload= column= table=     # cannot create table without output
 * -a from= to=                         # nothing to do
 *X-a from= to= upload= column=         # cannot update the "from" table
 *                                      # because #new != #"from"
 * -a from= to= upload= column= table=  # cannot create table without output
 *X-a from= to= output= upload= column= # cannot update the "from" table
 *                                      # because #new != #"from"
 *
 * X: manual dependency checks
 */

/* TODO: support all types (lines, boundaries, areas for 'from' (from_type) */

int main(int argc, char *argv[])
{
    struct GModule *module;
    struct {
        struct Option *from, *to, *from_type, *to_type, *from_field, *to_field;
        struct Option *out, *max, *min, *table;
        struct Option *upload, *column, *to_column;
        struct Option *sep;
    } opt;
    struct {
        struct Flag *print, *all, *square;
    } flag;

    int print;        /* -p: print to stdout */
    int create_map;   /* output=: create a new map */
    int create_table; /* table=: create a new table */
    int update_table; /* column= !table=: update the "from" table */
    int do_all;       /* -a: calculate from each to each within the threshold */
    int print_as_matrix; /* only for do_all=TRUE */

    int i, j;
    char *desc;
    struct Map_info From, To, Out, *Outp;
    int from_type, to_type, from_field, to_field, with_z;
    double max, min;
    double *max_step, max_map;
    int n_max_steps, curr_step;
    struct line_pnts *FPoints, *TPoints;
    struct line_cats *FCats, *TCats;
    NEAR *Near, *near;
    int anear;      /* allocated space, used only for do_all */
    UPLOAD *Upload; /* zero terminated */
    int ftype, ttype, fcat, nfcats, tcat, count, fline, tfeature;
    int nfrom, nfromlines, nfromareas, nto, ntolines, ntoareas;
    int tarea, area, isle, nisles, nlines;
    double fx, fy, fz, falong, fangle, tx, ty, tz, talong, tangle, dist;
    double tmp_fx, tmp_fy, tmp_fz, tmp_falong, tmp_fangle, tmp_dist;
    double tmp_tx, tmp_ty, tmp_tz, tmp_talong, tmp_tangle;
    int geodesic;
    struct field_info *Fi, *toFi;
    dbString stmt, dbstr;
    dbDriver *driver, *to_driver;
    int *catexist, ncatexist, *cex;
    char buf1[2000], buf2[2000], to_attr_sqltype[256];
    int update_ok, update_err, update_exist, update_notexist, update_dupl,
        update_notfound, sqltype;
    struct boxlist *lList, *aList;
    struct bound_box fbox, box;
    dbCatValArray cvarr;
    dbColumn *column;
    char *sep;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("distance"));
    G_add_keyword(_("database"));
    G_add_keyword(_("attribute table"));
    module->description = _("Finds the nearest element in vector map 'to' for "
                            "elements in vector map 'from'.");

    opt.from = G_define_standard_option(G_OPT_V_INPUT);
    opt.from->key = "from";
    opt.from->label = _("Name of existing vector map (from)");
    opt.from->guisection = _("From");

    opt.from_field = G_define_standard_option(G_OPT_V_FIELD);
    opt.from_field->key = "from_layer";
    opt.from_field->label = _("Layer number or name (from)");
    opt.from_field->guisection = _("From");

    opt.from_type = G_define_standard_option(G_OPT_V_TYPE);
    opt.from_type->key = "from_type";
    opt.from_type->options = "point,line,boundary,centroid,area";
    opt.from_type->answer = "point,line,area";
    opt.from_type->label = _("Feature type (from)");
    opt.from_type->guisection = _("From");

    opt.to = G_define_standard_option(G_OPT_V_INPUT);
    opt.to->key = "to";
    opt.to->label = _("Name of existing vector map (to)");
    opt.to->guisection = _("To");

    opt.to_field = G_define_standard_option(G_OPT_V_FIELD);
    opt.to_field->key = "to_layer";
    opt.to_field->label = _("Layer number or name (to)");
    opt.to_field->guisection = _("To");

    opt.to_type = G_define_standard_option(G_OPT_V_TYPE);
    opt.to_type->key = "to_type";
    opt.to_type->options = "point,line,boundary,centroid,area";
    opt.to_type->answer = "point,line,area";
    opt.to_type->label = _("Feature type (to)");
    opt.to_type->guisection = _("To");

    opt.out = G_define_standard_option(G_OPT_V_OUTPUT);
    opt.out->key = "output";
    opt.out->required = NO;
    opt.out->description = _("Name for output vector map containing lines "
                             "connecting nearest elements");

    opt.max = G_define_option();
    opt.max->key = "dmax";
    opt.max->type = TYPE_DOUBLE;
    opt.max->required = NO;
    opt.max->answer = "-1";
    opt.max->label = _("Maximum distance or -1 for no limit");
    opt.max->description = _("Map units, meters for ll");

    opt.min = G_define_option();
    opt.min->key = "dmin";
    opt.min->type = TYPE_DOUBLE;
    opt.min->required = NO;
    opt.min->answer = "-1";
    opt.min->label = _("Minimum distance or -1 for no limit");
    opt.min->description = _("Map units, meters for ll");

    opt.upload = G_define_option();
    opt.upload->key = "upload";
    opt.upload->type = TYPE_STRING;
    opt.upload->required = YES;
    opt.upload->multiple = YES;
    opt.upload->guisection = _("From");
    opt.upload->options = "cat,dist,to_x,to_y,to_along,to_angle,to_attr";
    opt.upload->description =
        _("Values describing the relation between two nearest features");
    desc = NULL;
    G_asprintf(
        &desc,
        "cat;%s;"
        "dist;%s;"
        "to_x;%s;"
        "to_y;%s;"
        "to_along;%s;"
        "to_angle;%s;"
        "to_attr;%s",
        _("category of the nearest feature"),
        _("minimum distance to nearest feature"),
        _("x coordinate of the nearest point on the 'to' feature"),
        _("y coordinate of the nearest point on the 'to' feature"),
        _("distance to the nearest point on the 'to' feature along "
          "that linear feature"),
        _("angle along the nearest linear feature in the 'to' map, "
          "measured CCW from the +x axis, in radians, between -Pi and Pi "
          "inclusive"),
        _("attribute of nearest feature given by to_column option"));
    /* "from_x - x coordinate of the nearest point on 'from' feature;"
     * "from_y - y coordinate of the nearest point on 'from' feature;"
     * "from_along - distance to the nearest point on 'from' feature along
     * linear feature;" "from_angle - angle between the linear feature in 'to'
     * map and the +x " "axis, at the location of point/centroid in 'from' map,
     * CCW, in " "radians, between -Pi and Pi inclusive;"
     */
    opt.upload->descriptions = desc;

    opt.column = G_define_standard_option(G_OPT_DB_COLUMN);
    opt.column->required = NO;
    opt.column->multiple = YES;
    opt.column->description = _("Column name(s) where values specified by "
                                "'upload' option will be uploaded");
    opt.column->guisection = _("From");

    opt.to_column = G_define_standard_option(G_OPT_DB_COLUMN);
    opt.to_column->key = "to_column";
    opt.to_column->description =
        _("Column name of nearest feature (used with upload=to_attr)");
    opt.to_column->guisection = _("To");

    opt.table = G_define_standard_option(G_OPT_DB_TABLE);
    opt.table->gisprompt = "new_dbtable,dbtable,dbtable";
    opt.table->description = _("Name for new attribute table");

    opt.sep = G_define_standard_option(G_OPT_F_SEP);
    opt.sep->label = _("Field separator for printing output to stdout");

    flag.print = G_define_flag();
    flag.print->key = 'p';
    flag.print->label =
        _("Print output to stdout, don't update attribute table");
    flag.print->description =
        _("First column is always category of 'from' feature called from_cat");

    flag.all = G_define_flag();
    flag.all->key = 'a';
    flag.all->label =
        _("Calculate distances to all features within the threshold");
    flag.all->description =
        _("Output may be written to stdout using the '-p' flag "
          "or uploaded to a new table created by the 'table' option; "
          "multiple 'upload' options may be used.");

    flag.square = G_define_flag();
    flag.square->key = 's';
    flag.square->label = _("Print output as square matrix (only possible for "
                           "one single upload variable)");

    /* GUI dependency */
    opt.from->guidependency = G_store(opt.from_field->key);
    sprintf(buf1, "%s,%s", opt.to_field->key, opt.to_column->key);
    opt.to->guidependency = G_store(buf1);
    opt.to_field->guidependency = G_store(opt.to_column->key);

    G_option_required(opt.out, opt.table, flag.print, opt.column, NULL);
    G_option_excludes(flag.print, opt.out, opt.table, NULL);
    G_option_requires(opt.upload, flag.print, opt.table, opt.column, NULL);
    G_option_requires(opt.column, opt.upload, NULL);
    G_option_requires(flag.print, opt.upload, NULL);
    G_option_requires_all(opt.table, opt.upload, NULL);

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

    geodesic = G_projection() == PROJECTION_LL;
    if (geodesic)
        line_distance = Vect_line_geodesic_distance;
    else
        line_distance = Vect_line_distance;

    from_type = Vect_option_to_types(opt.from_type);
    to_type = Vect_option_to_types(opt.to_type);

    max = atof(opt.max->answer);
    min = atof(opt.min->answer);

    print = flag.print->answer;
    create_map = opt.out->answer != NULL;
    create_table = opt.table->answer != NULL;
    update_table = !print && !create_table && opt.column->answer;
    do_all = flag.all->answer;
    print_as_matrix = flag.square->answer;

    if (do_all && update_table)
        G_fatal_error(_("Updating the from= table is not supported with -a"));

    /* Read upload and column options */
    /* count */
    i = 0;
    while (opt.upload->answer && opt.upload->answers[i])
        i++;

    if (i > 1 && print_as_matrix)
        G_fatal_error(_("Square matrix output only possible with one single "
                        "upload variable"));

    /* TODO: Known issue. Segmentation fault on print_as_matrix with dmin= or
     * dmax= because count may not be nfrom^2. Needs to populate Near[] fully
     * even if some near features are not found */
    if (print_as_matrix && (min >= 0 || max >= 0))
        G_fatal_error(
            _("Printing distance matrix is not supported with dmin= or dmax="));

    /* alloc */
    Upload = (UPLOAD *)G_calloc(i + 1, sizeof(UPLOAD));
    /* read upload */
    i = 0;
    while (opt.upload->answer && opt.upload->answers[i]) {
        if (strcmp(opt.upload->answers[i], "cat") == 0)
            Upload[i].upload = CAT;
        else if (strcmp(opt.upload->answers[i], "from_x") == 0)
            Upload[i].upload = FROM_X;
        else if (strcmp(opt.upload->answers[i], "from_y") == 0)
            Upload[i].upload = FROM_Y;
        else if (strcmp(opt.upload->answers[i], "to_x") == 0)
            Upload[i].upload = TO_X;
        else if (strcmp(opt.upload->answers[i], "to_y") == 0)
            Upload[i].upload = TO_Y;
        else if (strcmp(opt.upload->answers[i], "from_along") == 0)
            Upload[i].upload = FROM_ALONG;
        else if (strcmp(opt.upload->answers[i], "to_along") == 0)
            Upload[i].upload = TO_ALONG;
        else if (strcmp(opt.upload->answers[i], "dist") == 0)
            Upload[i].upload = DIST;
        else if (strcmp(opt.upload->answers[i], "to_angle") == 0)
            Upload[i].upload = TO_ANGLE;
        else if (strcmp(opt.upload->answers[i], "to_attr") == 0) {
            if (!(opt.to_column->answer)) {
                G_fatal_error(_("to_column option missing"));
            }
            Upload[i].upload = TO_ATTR;
        }

        /* store default column names if column= is not given */
        if (!opt.column->answer)
            Upload[i].column = G_store(opt.upload->answers[i]);

        i++;
    }
    Upload[i].upload = END;
    /* read columns */
    i = 0;
    while (opt.column->answer && opt.column->answers[i]) {
        if (Upload[i].upload == END) {
            G_warning(_("Too many column names"));
            break;
        }
        Upload[i].column = G_store(opt.column->answers[i]);
        i++;
    }
    if (opt.column->answer && Upload[i].upload != END)
        G_fatal_error(_("Not enough column names"));

    sep = G_option_to_separator(opt.sep);

    /* Open 'from' vector */
    Vect_set_open_level(2);
    if (Vect_open_old2(&From, opt.from->answer, "", opt.from_field->answer) < 0)
        G_fatal_error(_("Unable to open vector map <%s>"), opt.from->answer);

    from_field = Vect_get_field_number(&From, opt.from_field->answer);
    /* in 3D features may not be found */
    if (Vect_is_3d(&From))
        G_warning(_("Input vector map <%s> is 3D"), opt.from->answer);

    nfromlines = Vect_get_num_primitives(&From, from_type);
    nfromareas = 0;
    if (from_type & GV_AREA)
        nfromareas = Vect_get_num_areas(&From);

    nfrom = nfromlines + nfromareas;
    if (nfrom < 1) {
        const char *name = Vect_get_full_name(&From);

        Vect_close(&From);
        G_fatal_error(_("No features of selected type found in <%s>"), name);
    }

    /* Open 'to' vector */
    Vect_set_open_level(2);
    if (Vect_open_old2(&To, opt.to->answer, "", opt.to_field->answer) < 0)
        G_fatal_error(_("Unable to open vector map <%s>"), opt.to->answer);
    /* in 3D features may not be found */
    if (Vect_is_3d(&To))
        G_warning(_("Input vector map <%s> is 3D"), opt.to->answer);

    ntolines = Vect_get_num_primitives(&To, to_type);
    ntoareas = 0;
    if (to_type & GV_AREA)
        ntoareas = Vect_get_num_areas(&To);

    nto = ntolines + ntoareas;
    if (nto < 1) {
        const char *name = Vect_get_full_name(&To);

        Vect_close(&From);
        Vect_close(&To);
        G_fatal_error(_("No features of selected type found in <%s>"), name);
    }
    with_z = (Vect_is_3d(&From) && Vect_is_3d(&To));

    to_field = Vect_get_field_number(&To, opt.to_field->answer);

    /* Open output vector */
    if (create_map) {
        if (Vect_open_new(&Out, opt.out->answer, WITHOUT_Z) < 0)
            G_fatal_error(_("Unable to create vector map <%s>"),
                          opt.out->answer);

        Vect_hist_command(&Out);
        Outp = &Out;
    }
    else {
        Outp = NULL;
    }

    /* TODO: add maxdist = -1 to Vect_select_ !!! */
    /* Calc maxdist */
    n_max_steps = 1;
    max_map = max;
    if (max != 0) {
        double dx, dy, dz;

        Vect_get_map_box(&From, &fbox);
        Vect_get_map_box(&To, &box);

        Vect_box_extend(&fbox, &box);

        dx = fbox.E - fbox.W;
        dy = fbox.N - fbox.S;

        if (Vect_is_3d(&From))
            dz = fbox.T - fbox.B;
        else
            dz = 0.0;

        max_map = sqrt(dx * dx + dy * dy + dz * dz);
        if (max < 0) {
            if (geodesic)
                max = PORT_DOUBLE_MAX;
            else
                max = max_map;
        }

        /* how to determine a reasonable number of steps to increase the search
         * box? */
        /* with max > 0 but max <<< tmp_max, 2 steps are sufficient, first 0
         * then max a reasonable number of steps also depends on the number of
         * features in To e.g. only one area in To, no need to step */

        if (geodesic)
            n_max_steps = sqrt(nto);
        else
            n_max_steps = sqrt(nto) * max / max_map;
        /* max 9 steps from testing */
        if (n_max_steps > 9)
            n_max_steps = 9;
        if (n_max_steps < 3)
            n_max_steps = 3;
        if (n_max_steps > nto)
            n_max_steps = nto;

        G_debug(2, "max = %g", max);
        G_debug(2, "maximum reasonable search distance = %g", max_map);
        G_debug(2, "n 'to' features = %d", nto);
        G_debug(2, "n_max_steps = %d", n_max_steps);

        if (!geodesic)
            max_map = max;
    }

    if (min > max)
        G_fatal_error(_("dmin can not be larger than dmax"));

    if (n_max_steps > 1) {
        /* set up steps to increase search box */
        max_step = G_malloc(n_max_steps * sizeof(double));
        /* first step always 0 */
        if (geodesic)
            max_step[0] = 0;
        else
            max_step[0] = (min < 0 ? 0 : min);

        for (curr_step = 1; curr_step < n_max_steps - 1; curr_step++) {
            /* for 9 steps, this would be max / [128, 64, 32, 16, 8, 4, 2] */
            max_step[curr_step] =
                max_map / (2 << (n_max_steps - 1 - curr_step));
        }
        /* last step always max_map */
        max_step[n_max_steps - 1] = max_map;
        j = 1;
        for (i = 1; i < n_max_steps; i++) {
            if (max_step[j - 1] < max_step[i]) {
                max_step[j] = max_step[i];
                j++;
            }
        }
        n_max_steps = j;
        for (i = 0; i < n_max_steps; i++) {
            G_debug(2, "max step %d: %g", i, max_step[i]);
        }
    }
    else {
        max_step = G_malloc(sizeof(double));
        max_step[0] = max_map;
    }

    /* Open database driver */
    db_init_string(&stmt);
    db_init_string(&dbstr);
    driver = NULL;
    Fi = NULL;
    column = NULL;

    if (update_table) {
        Fi = Vect_get_field(&From, from_field);
        if (Fi == NULL)
            G_fatal_error(_("Database connection not defined for layer <%s>"),
                          opt.from_field->answer);

        driver = db_start_driver_open_database(Fi->driver, Fi->database);
        if (driver == NULL)
            G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
                          Fi->database, Fi->driver);

        /* check if column exists */
        i = 0;
        while (opt.column->answers[i]) {
            db_get_column(driver, Fi->table, opt.column->answers[i], &column);
            if (column) {
                db_free_column(column);
                column = NULL;
            }
            else {
                G_fatal_error(_("Column <%s> not found in table <%s>"),
                              opt.column->answers[i], Fi->table);
            }
            i++;
        }
        /* close db connection */
        db_close_database_shutdown_driver(driver);
        driver = NULL;
    }

    to_driver = NULL;
    toFi = NULL;
    if (opt.to_column->answer) {

        toFi = Vect_get_field(&To, to_field);
        if (toFi == NULL)
            G_fatal_error(_("Database connection not defined for layer %d"),
                          to_field);

        to_driver = db_start_driver_open_database(toFi->driver, toFi->database);
        if (to_driver == NULL)
            G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
                          toFi->database, toFi->driver);

        /* check if to_column exists and get its SQL type */
        db_get_column(to_driver, toFi->table, opt.to_column->answer, &column);
        if (column) {
            sqltype = db_get_column_sqltype(column);
            switch (sqltype) {
            case DB_SQL_TYPE_CHARACTER:
                sprintf(to_attr_sqltype, "VARCHAR(%d)",
                        db_get_column_length(column));
                break;
            default:
                sprintf(to_attr_sqltype, "%s", db_sqltype_name(sqltype));
            }

            db_free_column(column);
            column = NULL;
        }
        else {
            G_fatal_error(_("Column <%s> not found in table <%s>"),
                          opt.to_column->answer, toFi->table);
        }

        /* Check column types */
        if (update_table) {
            char *fcname = NULL;
            int fctype, tctype;

            i = 0;
            while (opt.column->answers[i]) {
                if (Upload[i].upload == TO_ATTR) {
                    fcname = opt.column->answers[i];
                    break;
                }
                i++;
            }

            if (fcname) {

                Fi = Vect_get_field(&From, from_field);
                if (Fi == NULL)
                    G_fatal_error(
                        _("Database connection not defined for layer <%s>"),
                        opt.from_field->answer);

                driver =
                    db_start_driver_open_database(Fi->driver, Fi->database);
                if (driver == NULL)
                    G_fatal_error(
                        _("Unable to open database <%s> by driver <%s>"),
                        Fi->database, Fi->driver);

                fctype = db_column_Ctype(driver, Fi->table, fcname);
                tctype = db_column_Ctype(to_driver, toFi->table,
                                         opt.to_column->answer);

                if (((tctype == DB_C_TYPE_STRING ||
                      tctype == DB_C_TYPE_DATETIME) &&
                     (fctype == DB_C_TYPE_INT || fctype == DB_C_TYPE_DOUBLE)) ||
                    ((tctype == DB_C_TYPE_INT || tctype == DB_C_TYPE_DOUBLE) &&
                     (fctype == DB_C_TYPE_STRING ||
                      fctype == DB_C_TYPE_DATETIME))) {
                    G_fatal_error(_("Incompatible column types"));
                }
                /* close db connection */
                db_close_database_shutdown_driver(driver);
                driver = NULL;
            }
        }
        /* close db connection */
        db_close_database_shutdown_driver(to_driver);
        to_driver = NULL;
    }

    FPoints = Vect_new_line_struct();
    TPoints = Vect_new_line_struct();
    FCats = Vect_new_cats_struct();
    TCats = Vect_new_cats_struct();
    lList = Vect_new_boxlist(1); /* line list */
    aList = Vect_new_boxlist(1); /* area list */

    /* Allocate space ( may be more than needed (duplicate cats and elements
     * without cats) ) */
    /* Be careful with do_all, it can easily run out of memory */
    anear = nfrom;
    Near = (NEAR *)G_calloc(nfrom, sizeof(NEAR));

    /* Read all cats from 'from' */
    nfcats = 0;
    if (!do_all) {
        nlines = Vect_get_num_lines(&From);
        for (i = 1; i <= nlines; i++) {
            ftype = Vect_read_line(&From, NULL, FCats, i);

            /* This keeps also categories of areas for future (if area s in
             * from_type) */
            if (!(ftype & from_type) &&
                (ftype != GV_CENTROID || !(from_type & GV_AREA)))
                continue;

            Vect_cat_get(FCats, from_field, &fcat);
            if (fcat < 0)
                continue;
            Near[nfcats].from_cat = fcat;
            Near[nfcats].dist = -1;
            Near[nfcats].count = 0;
            nfcats++;
        }
        G_debug(1, "%d cats loaded from vector (including duplicates)", nfcats);

        if (nfcats == 0)
            G_fatal_error(
                _("No categories for 'from' for selected type and layer"));

        /* Sort by cats and remove duplicates */
        qsort((void *)Near, nfcats, sizeof(NEAR), cmp_near);

        /* remove duplicates */
        j = 1;
        for (i = 1; i < nfcats; i++) {
            if (Near[i].from_cat != Near[j - 1].from_cat) {
                Near[j].from_cat = Near[i].from_cat;
                j++;
            }
        }
        nfcats = j;

        G_debug(1, "%d cats loaded from vector (unique)", nfcats);
    }

    /* Go through all lines in 'from' and find nearest in 'to' for each */
    /* Note: as from_type is restricted to GV_POINTS (for now) everything is
     * simple */

    /* suppress compiler warnings */
    tx = ty = tz = fx = fy = fz = 0;

    count = 0; /* count of distances in 'do_all' mode */
    /* Find nearest features for 'from' lines */
    if (nfromlines) {
        G_message(_("Finding nearest features..."));

        near = NULL;
        nlines = Vect_get_num_lines(&From);

        G_percent(0, nlines, 4);
        for (fline = 1; fline <= nlines; fline++) {
            int tmp_tcat;
            double tmp_min = (min < 0 ? 0 : min);
            double box_edge = 0;
            int done = FALSE;

            if (geodesic)
                tmp_min = 0;

            curr_step = 0;

            G_debug(3, "fline = %d", fline);
            G_percent(fline, nlines, 4);
            ftype = Vect_get_line_type(&From, fline);
            if (!(ftype & from_type))
                continue;

            Vect_read_line(&From, FPoints, FCats, fline);
            Vect_cat_get(FCats, from_field, &fcat);
            if (fcat < 0 && !do_all)
                continue;

            get_line_box(FPoints, &fbox);

            if (!do_all) {
                /* find near by 'from' cat */
                near = (NEAR *)bsearch((void *)&fcat, Near, nfcats,
                                       sizeof(NEAR), cmp_near);
            }

            dist = PORT_DOUBLE_MAX; /* distance to nearest 'to' feature */

            while (!done) {
                done = TRUE;

                tfeature = 0; /* id of nearest 'to' feature */

                if (!do_all) {
                    /* enlarge search box until we get a hit */
                    /* the objective is to enlarge the search box
                     * in the first iterations just a little bit
                     * to keep the number of hits low */
                    while (curr_step < n_max_steps) {
                        box_edge = max_step[curr_step];
                        curr_step++;

                        if (box_edge < tmp_min)
                            continue;

                        box.E = fbox.E + box_edge;
                        box.W = fbox.W - box_edge;
                        box.N = fbox.N + box_edge;
                        box.S = fbox.S - box_edge;
                        box.T = PORT_DOUBLE_MAX;
                        box.B = -PORT_DOUBLE_MAX;

                        if (ntolines)
                            Vect_select_lines_by_box(&To, &box, to_type, lList);
                        if (ntoareas)
                            Vect_select_areas_by_box(&To, &box, aList);

                        if (lList->n_values > 0 || aList->n_values > 0)
                            break;
                    }
                }
                else {
                    box.E = fbox.E + max_map;
                    box.W = fbox.W - max_map;
                    box.N = fbox.N + max_map;
                    box.S = fbox.S - max_map;
                    box.T = PORT_DOUBLE_MAX;
                    box.B = -PORT_DOUBLE_MAX;

                    if (ntolines)
                        Vect_select_lines_by_box(&To, &box, to_type, lList);
                    if (ntoareas)
                        Vect_select_areas_by_box(&To, &box, aList);
                }

                for (i = 0; i < lList->n_values; i++) {
                    ttype = Vect_read_line(&To, TPoints, TCats, lList->id[i]);

                    line2line(FPoints, ftype, TPoints, ttype, &tmp_fx, &tmp_fy,
                              &tmp_fz, &tmp_falong, &tmp_fangle, &tmp_tx,
                              &tmp_ty, &tmp_tz, &tmp_talong, &tmp_tangle,
                              &tmp_dist, with_z);

                    if (tmp_dist > max || tmp_dist < min)
                        continue; /* not in threshold */

                    tmp_tcat = -1;
                    /* TODO: all cats of given field ? */
                    for (j = 0; j < TCats->n_cats; j++) {
                        if (TCats->field[j] == to_field) {
                            if (tmp_tcat >= 0)
                                G_warning(
                                    _("More cats found in to_layer (line=%d)"),
                                    lList->id[i]);
                            tmp_tcat = TCats->cat[j];
                        }
                    }

                    G_debug(4, "  tmp_dist = %f tmp_tcat = %d", tmp_dist,
                            tmp_tcat);

                    if (do_all) {
                        if (anear <= count) {
                            anear += 10 + nto / 10;
                            Near =
                                (NEAR *)G_realloc(Near, anear * sizeof(NEAR));
                            if (!Near)
                                G_fatal_error(_("Out of memory!"));
                        }
                        near = &(Near[count]);

                        /* store info about relation */
                        near->from_cat = fcat;
                        near->to_cat = tmp_tcat; /* -1 is OK */
                        near->dist = tmp_dist;
                        near->from_x = tmp_fx;
                        near->from_y = tmp_fy;
                        near->from_z = tmp_fz;
                        near->from_along = tmp_falong; /* 0 for points */
                        near->from_angle = tmp_fangle;
                        near->to_x = tmp_tx;
                        near->to_y = tmp_ty;
                        near->to_z = tmp_tz;
                        near->to_along = tmp_talong; /* 0 for points */
                        near->to_angle = tmp_tangle;
                        near->count++;
                        count++;
                    }
                    else {
                        if (tfeature == 0 || (tmp_dist < dist)) {
                            tfeature = lList->id[i];
                            tcat = tmp_tcat;
                            dist = tmp_dist;
                            fx = tmp_fx;
                            fy = tmp_fy;
                            fz = tmp_fz;
                            falong = tmp_falong;
                            fangle = tmp_fangle;
                            tx = tmp_tx;
                            ty = tmp_ty;
                            tz = tmp_tz;
                            talong = tmp_talong;
                            tangle = tmp_tangle;
                        }
                    }
                }

                G_debug(3, "  %d areas in box", aList->n_values);

                for (i = 0; i < aList->n_values; i++) {
                    /* ignore isles OK ? */
                    if (Vect_get_area_centroid(&To, aList->id[i]) == 0)
                        continue;

                    line2area(&To, FPoints, ftype, aList->id[i], &aList->box[i],
                              &tmp_fx, &tmp_fy, &tmp_fz, &tmp_falong,
                              &tmp_fangle, &tmp_tx, &tmp_ty, &tmp_tz,
                              &tmp_talong, &tmp_tangle, &tmp_dist, with_z);

                    if (tmp_dist > max || tmp_dist < min)
                        continue; /* not in threshold */

                    /* TODO: more cats of the same field */
                    Vect_get_area_cats(&To, aList->id[i], TCats);
                    tmp_tcat = -1;

                    /* TODO: all cats of given field ? */
                    for (j = 0; j < TCats->n_cats; j++) {
                        if (TCats->field[j] == to_field) {
                            if (tmp_tcat >= 0)
                                G_warning(
                                    _("More cats found in to_layer (area=%d)"),
                                    aList->id[i]);
                            tmp_tcat = TCats->cat[j];
                        }
                    }

                    G_debug(4, "  tmp_dist = %f tmp_tcat = %d", tmp_dist,
                            tmp_tcat);

                    if (do_all) {
                        if (anear <= count) {
                            anear += 10 + nto / 10;
                            Near =
                                (NEAR *)G_realloc(Near, anear * sizeof(NEAR));
                            if (!Near)
                                G_fatal_error(_("Out of memory!"));
                        }
                        near = &(Near[count]);

                        /* store info about relation */
                        near->from_cat = fcat;
                        near->to_cat = tmp_tcat; /* -1 is OK */
                        near->dist = tmp_dist;
                        near->from_x = tmp_fx;
                        near->from_y = tmp_fy;
                        near->from_z = tmp_fz;
                        near->from_along = tmp_falong; /* 0 for points */
                        near->from_angle = tmp_fangle;
                        near->to_x = tmp_tx;
                        near->to_y = tmp_ty;
                        near->to_z = tmp_tz;
                        near->to_along = tmp_talong; /* 0 for points */
                        near->to_angle = tmp_tangle;
                        near->count++;
                        count++;
                    }
                    else {
                        if (tfeature == 0 || (tmp_dist < dist)) {
                            tfeature = aList->id[i];
                            tcat = tmp_tcat;
                            dist = tmp_dist;
                            fx = tmp_fx;
                            fy = tmp_fy;
                            fz = tmp_fz;
                            falong = tmp_falong;
                            fangle = tmp_fangle;
                            tx = tmp_tx;
                            ty = tmp_ty;
                            tz = tmp_tz;
                            talong = tmp_talong;
                            tangle = tmp_tangle;
                        }
                    }
                }

                G_debug(4, "  dist = %f", dist);

                if (!do_all && curr_step < n_max_steps) {
                    double dist_map = dist;

                    if (geodesic && tfeature > 0) {
                        double dx = fx - tx;
                        double dy = fy - ty;
                        double dz = fz - tz;

                        dist_map = sqrt(dx * dx + dy * dy + dz * dz);
                    }
                    /* enlarging the search box is possible */
                    if (tfeature > 0 && dist_map > box_edge) {
                        /* line found but distance > search edge:
                         * line bbox overlaps with search box, line itself is
                         * outside search box */
                        done = FALSE;
                    }
                    else if (tfeature == 0) {
                        /* no line within max dist, but search box can still be
                         * enlarged */
                        done = FALSE;
                    }
                }
                if (done && !do_all && tfeature > 0) {

                    G_debug(4, "  near->from_cat = %d near->count = %d",
                            near->from_cat, near->count);
                    /* store info about relation */
                    if (near->count == 0 || near->dist > dist) {
                        near->to_cat = tcat; /* -1 is OK */
                        near->dist = dist;
                        near->from_x = fx;
                        near->from_y = fy;
                        near->from_z = fz;
                        near->from_along = falong; /* 0 for points */
                        near->from_angle = fangle;
                        near->to_x = tx;
                        near->to_y = ty;
                        near->to_z = tz;
                        near->to_along = talong; /* 0 for points */
                        near->to_angle = tangle;
                    }
                    near->count++;
                }
            } /* done searching 'to' */
        }     /* next from feature */
    }

    /* Find nearest features for 'from' areas */
    /* the code is pretty much identical to the one for lines */
    if (nfromareas) {

        near = NULL;

        G_message(_("Finding nearest features for areas..."));
        G_percent(0, nfromareas, 2);
        for (area = 1; area <= nfromareas; area++) {
            int tmp_tcat;
            double tmp_min = (min < 0 ? 0 : min);
            double box_edge = 0;
            int done = FALSE;

            if (geodesic)
                tmp_min = 0;

            curr_step = 0;

            G_debug(3, "farea = %d", area);
            G_percent(area, nfromareas, 2);

            if (Vect_get_area_cats(&From, area, FCats) == 1)
                /* ignore isles OK ? */
                continue;

            Vect_cat_get(FCats, from_field, &fcat);
            if (fcat < 0 && !do_all)
                continue;

            Vect_get_area_box(&From, area, &fbox);
            Vect_reset_line(FPoints);

            if (!do_all) {
                /* find near by 'from' cat */
                near = (NEAR *)bsearch((void *)&fcat, Near, nfcats,
                                       sizeof(NEAR), cmp_near);
            }

            dist = PORT_DOUBLE_MAX; /* distance to nearest 'to' feature */

            while (!done) {
                done = TRUE;

                tfeature = 0; /* id of nearest 'to' feature */

                if (!do_all) {
                    /* enlarge search box until we get a hit */
                    /* the objective is to enlarge the search box
                     * in the first iterations just a little bit
                     * to keep the number of hits low */
                    while (curr_step < n_max_steps) {
                        box_edge = max_step[curr_step];
                        curr_step++;

                        if (box_edge < tmp_min)
                            continue;

                        box.E = fbox.E + box_edge;
                        box.W = fbox.W - box_edge;
                        box.N = fbox.N + box_edge;
                        box.S = fbox.S - box_edge;
                        box.T = PORT_DOUBLE_MAX;
                        box.B = -PORT_DOUBLE_MAX;

                        if (ntolines)
                            Vect_select_lines_by_box(&To, &box, to_type, lList);
                        if (ntoareas)
                            Vect_select_areas_by_box(&To, &box, aList);

                        if (lList->n_values > 0 || aList->n_values > 0)
                            break;
                    }
                }
                else {
                    box.E = fbox.E + max_map;
                    box.W = fbox.W - max_map;
                    box.N = fbox.N + max_map;
                    box.S = fbox.S - max_map;
                    box.T = PORT_DOUBLE_MAX;
                    box.B = -PORT_DOUBLE_MAX;

                    if (ntolines)
                        Vect_select_lines_by_box(&To, &box, to_type, lList);
                    if (ntoareas)
                        Vect_select_areas_by_box(&To, &box, aList);
                }

                G_debug(3, "  %d lines in box", lList->n_values);

                for (i = 0; i < lList->n_values; i++) {
                    ttype = Vect_read_line(&To, TPoints, TCats, lList->id[i]);

                    /* area to line */
                    line2area(&From, TPoints, ttype, area, &fbox, &tmp_tx,
                              &tmp_ty, &tmp_tz, &tmp_talong, &tmp_tangle,
                              &tmp_fx, &tmp_fy, &tmp_fz, &tmp_falong,
                              &tmp_fangle, &tmp_dist, with_z);

                    if (tmp_dist > max || tmp_dist < min)
                        continue; /* not in threshold */

                    tmp_tcat = -1;
                    /* TODO: all cats of given field ? */
                    for (j = 0; j < TCats->n_cats; j++) {
                        if (TCats->field[j] == to_field) {
                            if (tmp_tcat >= 0)
                                G_warning(
                                    _("More cats found in to_layer (line=%d)"),
                                    lList->id[i]);
                            tmp_tcat = TCats->cat[j];
                        }
                    }

                    G_debug(4, "  tmp_dist = %f tmp_tcat = %d", tmp_dist,
                            tmp_tcat);

                    if (do_all) {
                        if (anear <= count) {
                            anear += 10 + nto / 10;
                            Near =
                                (NEAR *)G_realloc(Near, anear * sizeof(NEAR));
                            if (!Near)
                                G_fatal_error(_("Out of memory!"));
                        }
                        near = &(Near[count]);

                        /* store info about relation */
                        near->from_cat = fcat;
                        near->to_cat = tmp_tcat; /* -1 is OK */
                        near->dist = tmp_dist;
                        near->from_x = tmp_fx;
                        near->from_y = tmp_fy;
                        near->from_z = tmp_fz;
                        near->from_along = tmp_falong; /* 0 for points */
                        near->from_angle = tmp_fangle;
                        near->to_x = tmp_tx;
                        near->to_y = tmp_ty;
                        near->to_z = tmp_tz;
                        near->to_along = tmp_talong; /* 0 for points */
                        near->to_angle = tmp_tangle;
                        near->count++;
                        count++;
                    }
                    else {
                        if (tfeature == 0 || (tmp_dist < dist)) {
                            tfeature = lList->id[i];
                            tcat = tmp_tcat;
                            dist = tmp_dist;
                            fx = tmp_fx;
                            fy = tmp_fy;
                            fz = tmp_fz;
                            falong = tmp_falong;
                            fangle = tmp_fangle;
                            tx = tmp_tx;
                            ty = tmp_ty;
                            tz = tmp_tz;
                            talong = tmp_talong;
                            tangle = tmp_tangle;
                        }
                    }
                }

                G_debug(3, "  %d areas in box", aList->n_values);

                /* For each area in box check the distance */
                for (i = 0; i < aList->n_values; i++) {
                    int poly;

                    tarea = aList->id[i];
                    G_debug(4, "%d: 'to' area id %d", i, tarea);

                    /* ignore isles OK ? */
                    if (Vect_get_area_centroid(&To, tarea) == 0)
                        continue;

                    Vect_get_area_points(&To, tarea, TPoints);

                    ttype = GV_BOUNDARY;

                    /* Find the distance of the outer ring of 'to' area
                     * to 'from' area */
                    poly = line2area(
                        &From, TPoints, ttype, area, &fbox, &tmp_tx, &tmp_ty,
                        &tmp_tz, &tmp_talong, &tmp_tangle, &tmp_fx, &tmp_fy,
                        &tmp_fz, &tmp_falong, &tmp_fangle, &tmp_dist, with_z);

                    if (poly == 3) {
                        /* 'to' outer ring is outside 'from' area,
                         * check if 'from' area is inside 'to' area */
                        poly = 0;
                        /* boxes must overlap */
                        if (Vect_box_overlap(&fbox, &aList->box[i])) {
                            if (FPoints->n_points == 0)
                                Vect_get_area_points(&From, area, FPoints);
                            for (j = 0; j < FPoints->n_points; j++) {
                                poly = Vect_point_in_poly(
                                    FPoints->x[j], FPoints->y[j], TPoints);
                                if (poly)
                                    break;
                            }
                        }
                        if (poly) {
                            /* 'from' area is (partially) inside 'to' area,
                             * get distance to 'to' area */
                            if (FPoints->n_points == 0)
                                Vect_get_area_points(&From, area, FPoints);
                            poly = line2area(
                                &To, FPoints, ttype, tarea, &aList->box[i],
                                &tmp_fx, &tmp_fy, &tmp_fz, &tmp_falong,
                                &tmp_fangle, &tmp_tx, &tmp_ty, &tmp_tz,
                                &tmp_talong, &tmp_tangle, &tmp_dist, with_z);

                            /* inside isle ? */
                            poly = poly == 2;
                        }
                        if (poly == 1) {
                            double tmp2_tx, tmp2_ty, tmp2_tz, tmp2_talong,
                                tmp2_tangle;
                            double tmp2_fx, tmp2_fy, tmp2_fz, tmp2_falong,
                                tmp2_fangle;
                            double tmp2_dist;

                            /* 'from' area is (partially) inside 'to' area,
                             * get distance to 'to' isles */
                            nisles = Vect_get_area_num_isles(&To, tarea);
                            for (j = 0; j < nisles; j++) {
                                isle = Vect_get_area_isle(&To, tarea, j);
                                Vect_get_isle_points(&To, isle, TPoints);

                                line2area(&From, TPoints, ttype, area, &fbox,
                                          &tmp2_tx, &tmp2_ty, &tmp2_tz,
                                          &tmp2_talong, &tmp2_tangle, &tmp2_fx,
                                          &tmp2_fy, &tmp2_fz, &tmp2_falong,
                                          &tmp2_fangle, &tmp2_dist, with_z);

                                if (tmp2_dist < tmp_dist) {
                                    tmp_dist = tmp2_dist;
                                    tmp_fx = tmp2_fx;
                                    tmp_fy = tmp2_fy;
                                    tmp_fz = tmp2_fz;
                                    tmp_falong = tmp2_falong;
                                    tmp_fangle = tmp2_fangle;
                                    tmp_tx = tmp2_tx;
                                    tmp_ty = tmp2_ty;
                                    tmp_tz = tmp2_tz;
                                    tmp_talong = tmp2_talong;
                                    tmp_tangle = tmp2_tangle;
                                }
                            }
                        }
                    }

                    if (tmp_dist > max || tmp_dist < min)
                        continue; /* not in threshold */
                    Vect_get_area_cats(&To, tarea, TCats);
                    tmp_tcat = -1;
                    /* TODO: all cats of given field ? */
                    for (j = 0; j < TCats->n_cats; j++) {
                        if (TCats->field[j] == to_field) {
                            if (tmp_tcat >= 0)
                                G_warning(
                                    _("More cats found in to_layer (area=%d)"),
                                    tarea);
                            tmp_tcat = TCats->cat[j];
                        }
                    }

                    G_debug(4, "  tmp_dist = %f tmp_tcat = %d", tmp_dist,
                            tmp_tcat);

                    if (do_all) {
                        if (anear <= count) {
                            anear += 10 + nfrom / 10;
                            Near =
                                (NEAR *)G_realloc(Near, anear * sizeof(NEAR));
                        }
                        near = &(Near[count]);

                        /* store info about relation */
                        near->from_cat = fcat;
                        near->to_cat = tmp_tcat; /* -1 is OK */
                        near->dist = tmp_dist;
                        near->from_x = tmp_fx;
                        near->from_y = tmp_fy;
                        near->from_z = tmp_fz;
                        near->from_along = tmp_falong; /* 0 for points */
                        near->from_angle = tmp_fangle;
                        near->to_x = tmp_tx;
                        near->to_y = tmp_ty;
                        near->to_z = tmp_tz;
                        near->to_along = tmp_talong; /* 0 for points */
                        near->to_angle = tmp_tangle;
                        near->count++;
                        count++;
                    }
                    else {
                        if (tfeature == 0 || tmp_dist < dist) {
                            tfeature = tarea;
                            tcat = tmp_tcat;
                            dist = tmp_dist;
                            fx = tmp_fx;
                            fy = tmp_fy;
                            fz = tmp_fz;
                            falong = tmp_falong;
                            fangle = tmp_fangle;
                            tx = tmp_tx;
                            ty = tmp_ty;
                            tz = tmp_tz;
                            talong = tmp_talong;
                            tangle = tmp_tangle;
                        }
                    }
                }

                if (!do_all && curr_step < n_max_steps) {
                    double dist_map = dist;

                    if (geodesic && tfeature > 0) {
                        double dx = fx - tx;
                        double dy = fy - ty;
                        double dz = fz - tz;

                        dist_map = sqrt(dx * dx + dy * dy + dz * dz);
                    }
                    /* enlarging the search box is possible */
                    if (tfeature > 0 && dist_map > box_edge) {
                        /* area found but distance > search edge:
                         * area bbox overlaps with search box, area itself is
                         * outside search box */
                        done = FALSE;
                    }
                    else if (tfeature == 0) {
                        /* no area within max dist, but search box can still be
                         * enlarged */
                        done = FALSE;
                    }
                }
                if (done && !do_all && tfeature > 0) {

                    G_debug(4, "near.from_cat = %d near.count = %d dist = %f",
                            near->from_cat, near->count, near->dist);

                    /* store info about relation */
                    if (near->count == 0 || near->dist > dist) {
                        near->to_cat = tcat; /* -1 is OK */
                        near->dist = dist;
                        near->from_x = fx;
                        near->from_y = fy;
                        near->from_z = fz;
                        near->from_along = falong;
                        near->from_angle = fangle;
                        near->to_x = tx;
                        near->to_y = ty;
                        near->to_z = tz;
                        near->to_along = talong;
                        near->to_angle = tangle;
                    }
                    near->count++;
                }
            } /* done */
        }     /* next feature */
    }

    G_debug(3, "count = %d", count);

    /* select 'to' attributes */
    if (opt.to_column->answer) {
        int nrec;

        to_driver = db_start_driver_open_database(toFi->driver, toFi->database);
        if (to_driver == NULL)
            G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
                          toFi->database, toFi->driver);

        db_CatValArray_init(&cvarr);
        nrec = db_select_CatValArray(to_driver, toFi->table, toFi->key,
                                     opt.to_column->answer, NULL, &cvarr);
        G_debug(3, "selected values = %d", nrec);

        if (cvarr.ctype == DB_C_TYPE_DATETIME) {
            G_warning(_("DATETIME type not yet supported, no attributes will "
                        "be uploaded"));
        }
        db_close_database_shutdown_driver(to_driver);
        to_driver = NULL;
    }

    /* open from driver */
    if (update_table) {
        driver = db_start_driver_open_database(Fi->driver, Fi->database);
        if (driver == NULL)
            G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
                          Fi->database, Fi->driver);
    }
    else if (create_table) {
        driver = db_start_driver_open_database(NULL, NULL);
        if (driver == NULL)
            G_fatal_error(_("Unable to open default database"));
    }

    update_ok = update_err = update_exist = update_notexist = update_dupl =
        update_notfound = ncatexist = 0;

    /* Update database / print to stdout / create output map */
    if (print) { /* print header */
        fprintf(stdout, "from_cat");
        if (do_all)
            fprintf(stdout, "%sto_cat", sep);
        i = 0;
        while (Upload[i].upload != END) {
            fprintf(stdout, "%s%s", sep, Upload[i].column);
            i++;
        }
        fprintf(stdout, "\n");
    }
    else if (create_table) { /* create new table */
        db_set_string(&stmt, "create table ");
        db_append_string(&stmt, opt.table->answer);
        if (Outp)
            db_append_string(&stmt, " (cat integer,from_cat integer");
        else
            db_append_string(&stmt, " (from_cat integer");

        j = 0;
        while (Upload[j].upload != END) {
            db_append_string(&stmt, ", ");

            switch (Upload[j].upload) {
            case CAT:
                sprintf(buf2, "%s integer", Upload[j].column);
                break;
            case DIST:
            case FROM_X:
            case FROM_Y:
            case TO_X:
            case TO_Y:
            case FROM_ALONG:
            case TO_ALONG:
            case TO_ANGLE:
                sprintf(buf2, "%s double precision", Upload[j].column);
                break;
            case TO_ATTR:
                sprintf(buf2, "%s %s", Upload[j].column, to_attr_sqltype);
            }
            db_append_string(&stmt, buf2);
            j++;
        }
        db_append_string(&stmt, " )");
        G_debug(3, "SQL: %s", db_get_string(&stmt));

        if (db_execute_immediate(driver, &stmt) != DB_OK)
            G_fatal_error(_("Unable to create table: '%s'"),
                          db_get_string(&stmt));

        if (db_grant_on_table(driver, opt.table->answer, DB_PRIV_SELECT,
                              DB_GROUP | DB_PUBLIC) != DB_OK)
            G_fatal_error(_("Unable to grant privileges on table <%s>"),
                          opt.table->answer);
    }
    else if (update_table) { /* read existing cats from table */
        ncatexist = db_select_int(driver, Fi->table, Fi->key, NULL, &catexist);
        G_debug(1, "%d cats selected from the table", ncatexist);
    }

    if (!do_all) {
        count = nfcats;
    }
    else {
        qsort((void *)Near, count, sizeof(NEAR), cmp_near_to);
    }

    if (driver)
        db_begin_transaction(driver);

    if (!print) /* no printing */
        G_message("Update vector attributes...");

    for (i = 0; i < count; i++) {
        dbCatVal *catval = 0;

        if (!print) /* no printing */
            G_percent(i, count, 1);

        /* Write line connecting nearest points */
        if (Near[i].count > 0 && Outp != NULL) {
            Vect_reset_line(FPoints);
            Vect_reset_cats(FCats);

            Vect_cat_set(FCats, 1, i);

            Vect_append_point(FPoints, Near[i].from_x, Near[i].from_y, 0);

            if (Near[i].dist == 0) {
                Vect_write_line(Outp, GV_POINT, FPoints, FCats);
            }
            else {
                Vect_append_point(FPoints, Near[i].to_x, Near[i].to_y, 0);
                Vect_write_line(Outp, GV_LINE, FPoints, FCats);
            }
        }

        if (Near[i].count > 1)
            update_dupl++;
        if (Near[i].count == 0)
            update_notfound++;

        if (opt.to_column->answer && Near[i].count > 0) {
            db_CatValArray_get_value(&cvarr, Near[i].to_cat, &catval);
        }

        if (print) { /* print only */
            /*
               input and output is the same &&
               calculate distances &&
               only one upload option given ->
               print as a matrix
             */
            if (print_as_matrix) {
                if (i == 0) {
                    for (j = 0; j < nfrom; j++) {
                        if (j == 0)
                            fprintf(stdout, " ");
                        fprintf(stdout, "%s%d", sep, Near[j].to_cat);
                    }
                    fprintf(stdout, "\n");
                }
                if (i % nfrom == 0) {
                    fprintf(stdout, "%d", Near[i].from_cat);
                    for (j = 0; j < nfrom; j++) {
                        print_upload(Near, Upload, i + j, &cvarr, catval, sep);
                    }
                    fprintf(stdout, "\n");
                }
            }
            else {
                fprintf(stdout, "%d", Near[i].from_cat);
                if (do_all)
                    fprintf(stdout, "%s%d", sep, Near[i].to_cat);
                print_upload(Near, Upload, i, &cvarr, catval, sep);
                fprintf(stdout, "\n");
            }
        }
        else if (create_table) {    /* insert new record */
            if (Near[i].count == 0) /* no nearest found */
                continue;

            if (!Outp)
                sprintf(buf1, "insert into %s values ( %d ", opt.table->answer,
                        Near[i].from_cat);
            else
                sprintf(buf1, "insert into %s values ( %d, %d ",
                        opt.table->answer, i, Near[i].from_cat);

            db_set_string(&stmt, buf1);

            j = 0;
            while (Upload[j].upload != END) {
                db_append_string(&stmt, ",");

                switch (Upload[j].upload) {
                case CAT:
                    sprintf(buf2, " %d", Near[i].to_cat);
                    break;
                case DIST:
                    sprintf(buf2, " %.17g", Near[i].dist);
                    break;
                case FROM_X:
                    sprintf(buf2, " %.17g", Near[i].from_x);
                    break;
                case FROM_Y:
                    sprintf(buf2, " %.17g", Near[i].from_y);
                    break;
                case TO_X:
                    sprintf(buf2, " %.17g", Near[i].to_x);
                    break;
                case TO_Y:
                    sprintf(buf2, " %.17g", Near[i].to_y);
                    break;
                case FROM_ALONG:
                    sprintf(buf2, " %.17g", Near[i].from_along);
                    break;
                case TO_ALONG:
                    sprintf(buf2, " %.17g", Near[i].to_along);
                    break;
                case TO_ANGLE:
                    sprintf(buf2, " %.17g", Near[i].to_angle);
                    break;
                case TO_ATTR:
                    if (catval) {
                        switch (cvarr.ctype) {
                        case DB_C_TYPE_INT:
                            sprintf(buf2, " %d", catval->val.i);
                            break;

                        case DB_C_TYPE_DOUBLE:
                            sprintf(buf2, " %.17g", catval->val.d);
                            break;

                        case DB_C_TYPE_STRING:
                            db_set_string(&dbstr, db_get_string(catval->val.s));
                            db_double_quote_string(&dbstr);
                            sprintf(buf2, " '%s'", db_get_string(&dbstr));
                            break;

                        case DB_C_TYPE_DATETIME:
                            /* TODO: formatting datetime */
                            sprintf(buf2, " null");
                            break;
                        }
                    }
                    else {
                        sprintf(buf2, " null");
                    }
                    break;
                }
                db_append_string(&stmt, buf2);
                j++;
            }
            db_append_string(&stmt, " )");
            G_debug(3, "SQL: %s", db_get_string(&stmt));
            if (db_execute_immediate(driver, &stmt) == DB_OK) {
                update_ok++;
            }
            else {
                update_err++;
            }
        }
        else if (update_table) { /* update table */
            int do_update = 0;

            /* check if exists in table */
            cex = (int *)bsearch((void *)&(Near[i].from_cat), catexist,
                                 ncatexist, sizeof(int), cmp_exist);
            if (cex == NULL) { /* cat does not exist in DB */
                update_notexist++;
                continue;
            }
            update_exist++;

            sprintf(buf1, "update %s set", Fi->table);
            db_set_string(&stmt, buf1);

            j = 0;
            while (Upload[j].upload != END) {
                if (j > 0)
                    db_append_string(&stmt, ",");

                sprintf(buf2, " %s =", Upload[j].column);
                db_append_string(&stmt, buf2);

                if (Near[i].count == 0) { /* no nearest found */
                    /* really clear existing records if no nearest found ? */
                    db_append_string(&stmt, " null");
                }
                else {
                    do_update = 1;
                    switch (Upload[j].upload) {
                    case CAT:
                        if (Near[i].to_cat > 0)
                            sprintf(buf2, " %d", Near[i].to_cat);
                        else
                            sprintf(buf2, " null");
                        break;
                    case DIST:
                        sprintf(buf2, " %.17g", Near[i].dist);
                        break;
                    case FROM_X:
                        sprintf(buf2, " %.17g", Near[i].from_x);
                        break;
                    case FROM_Y:
                        sprintf(buf2, " %.17g", Near[i].from_y);
                        break;
                    case TO_X:
                        sprintf(buf2, " %.17g", Near[i].to_x);
                        break;
                    case TO_Y:
                        sprintf(buf2, " %.17g", Near[i].to_y);
                        break;
                    case FROM_ALONG:
                        sprintf(buf2, " %.17g", Near[i].from_along);
                        break;
                    case TO_ALONG:
                        sprintf(buf2, " %.17g", Near[i].to_along);
                        break;
                    case TO_ANGLE:
                        sprintf(buf2, " %.17g", Near[i].to_angle);
                        break;
                    case TO_ATTR:
                        if (catval) {
                            switch (cvarr.ctype) {
                            case DB_C_TYPE_INT:
                                sprintf(buf2, " %d", catval->val.i);
                                break;

                            case DB_C_TYPE_DOUBLE:
                                sprintf(buf2, " %.17g", catval->val.d);
                                break;

                            case DB_C_TYPE_STRING:
                                db_set_string(&dbstr,
                                              db_get_string(catval->val.s));
                                db_double_quote_string(&dbstr);
                                sprintf(buf2, " '%s'", db_get_string(&dbstr));
                                break;

                            case DB_C_TYPE_DATETIME:
                                /* TODO: formatting datetime */
                                sprintf(buf2, " null");
                                break;
                            }
                        }
                        else {
                            sprintf(buf2, " null");
                        }
                        break;
                    }
                    db_append_string(&stmt, buf2);
                }
                j++;
            }
            if (do_update) {
                sprintf(buf2, " where %s = %d", Fi->key, Near[i].from_cat);
                db_append_string(&stmt, buf2);
                G_debug(2, "SQL: %s", db_get_string(&stmt));
                if (db_execute_immediate(driver, &stmt) == DB_OK) {
                    update_ok++;
                }
                else {
                    update_err++;
                }
            }
        }
    }
    G_percent(count, count, 1);

    if (driver)
        db_commit_transaction(driver);

    /* print stats */
    if (update_dupl > 0)
        G_message(
            _("%d categories with more than 1 feature in vector map <%s>"),
            update_dupl, opt.from->answer);
    if (update_notfound > 0)
        G_message(_("%d categories - no nearest feature found"),
                  update_notfound);

    if (update_table || create_table) {
        db_close_database_shutdown_driver(driver);
        db_free_string(&stmt);

        /* print stats */
        if (create_table) {
            G_message(_("%d distances calculated"), count);
            G_message(_("%d records inserted"), update_ok);
            if (update_err > 0)
                G_message(_("%d insert errors"), update_err);
        }
        else {
            if (nfcats > 0)
                G_verbose_message(_("%d categories read from the map"), nfcats);
            if (ncatexist > 0)
                G_verbose_message(_("%d categories exist in the table"),
                                  ncatexist);
            if (update_exist > 0)
                G_verbose_message(
                    _("%d categories read from the map exist in the table"),
                    update_exist);
            if (update_notexist > 0)
                G_verbose_message(_("%d categories read from the map don't "
                                    "exist in the table"),
                                  update_notexist);
            if (update_err > 0)
                G_warning(_("%d update errors"), update_err);

            G_done_msg(_("%d records updated."), update_ok);

            G_free(catexist);
            Vect_set_db_updated(&From);
        }
    }

    Vect_close(&From);
    if (Outp != NULL) {
        if (create_table) {
            dbConnection connection;

            db_get_connection(&connection);
            Vect_map_add_dblink(Outp, 1, NULL, opt.table->answer, "cat",
                                connection.databaseName, connection.driverName);
        }
        Vect_build(Outp);
        Vect_close(Outp);
    }

    exit(EXIT_SUCCESS);
}
