/***************************************************************
 *
 * MODULE:       v.out.ogr
 *
 * AUTHOR(S):    Radim Blazek
 *               Some extensions: Markus Neteler, Benjamin Ducke
 *               Multi-features support by Martin Landa <landa.martin gmail.com>
 *
 * PURPOSE:      Converts GRASS vector to one of supported OGR vector formats.
 *
 * COPYRIGHT:    (C) 2001-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 <string.h>

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

#include "local_proto.h"

#include <ogr_srs_api.h>

int main(int argc, char *argv[])
{
    int i, otype, ftype, donocat;
    int num_to_export;
    int field;
    int overwrite, found;

    struct GModule *module;
    struct Options options;
    struct Flags flags;

    char buf[SQL_BUFFER_SIZE];
    char key1[SQL_BUFFER_SIZE], key2[SQL_BUFFER_SIZE];
    struct Cell_head cellhd;
    char **tokens;

    /* Vector */
    struct Map_info In;

    /* Attributes */
    int doatt = 0, ncol = 0, colsqltype, colwidth, keycol = -1;
    int *colctype = NULL;
    const char **colname = NULL;
    struct field_info *Fi = NULL;
    dbDriver *Driver = NULL;
    dbTable *Table;
    dbString dbstring;
    dbColumn *Column;

    /* number of written features */
    int n_feat;
    /* number of features without cats/atts written/skip */
    int n_nocat, n_noatt;

    /* OGR */
    int drn;
    OGRFieldType ogr_ftype = OFTInteger;
    GDALDatasetH hDS;
    GDALDriverH hDriver;
    OGRLayerH Ogr_layer;
    OGRFieldDefnH Ogr_field;
    OGRFeatureDefnH Ogr_featuredefn;
    OGRwkbGeometryType wkbtype = wkbUnknown;
    OGRSpatialReferenceH Ogr_projection;
    char **papszDSCO = NULL, **papszLCO = NULL;
    int num_types;
    char *dsn;
    int outer_ring_ccw;

    G_gisinit(argv[0]);

    /* Module options */
    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("export"));
    G_add_keyword(_("output"));
    G_add_keyword("OGR");

    module->label = _("Exports a vector map layer to any of the supported OGR "
                      "vector formats.");
    module->description = _(
        "By default a vector map layer is exported to OGC GeoPackage format.");
    module->overwrite = TRUE;

    /* parse & read options */
    parse_args(argc, argv, &options, &flags);

    if (flags.list->answer) {
        list_formats();
        exit(EXIT_SUCCESS);
    }

    /* check for weird options */
    if (G_strncasecmp(options.dsn->answer, "PG:", 3) == 0 &&
        strcmp(options.format->answer, "PostgreSQL") != 0)
        G_warning(_("Data source starts with \"PG:\" prefix, expecting "
                    "\"PostgreSQL\" "
                    "format (\"%s\" given)"),
                  options.format->answer);

    /* parse dataset creation options */
    i = 0;
    while (options.dsco->answers[i]) {
        tokens = G_tokenize(options.dsco->answers[i], "=");
        if (G_number_of_tokens(tokens))
            papszDSCO = CSLSetNameValue(papszDSCO, tokens[0], tokens[1]);
        G_free_tokens(tokens);
        i++;
    }

    /* parse layer creation options */
    i = 0;
    while (options.lco->answers[i]) {
        tokens = G_tokenize(options.lco->answers[i], "=");
        if (G_number_of_tokens(tokens))
            papszLCO = CSLSetNameValue(papszLCO, tokens[0], tokens[1]);
        G_free_tokens(tokens);
        i++;
    }

    /*
       If no output type specified: determine one automatically.
       Centroids, Boundaries and Kernels always have to be exported
       explicitly, using the "type=" option.
     */
    field = 0;
    if (!flags.new->answer) {
        /* open input vector (topology required) */
        Vect_set_open_level(2);
        if (Vect_open_old2(&In, options.input->answer, "",
                           options.field->answer) < 0)
            G_fatal_error(_("Unable to open vector map <%s>"),
                          options.input->answer);

        if (strcmp(options.type->answer, "auto") == 0) {
            G_debug(2, "Automatic type determination.");

            options.type->answers = G_malloc(sizeof(char *) * 10);
            G_zero(options.type->answers, sizeof(char *) * 10);
            num_types = 0;

            if (Vect_get_num_primitives(&In, GV_POINT) > 0) {
                options.type->answers[num_types++] = G_store("point");
                G_debug(3, "Adding points to export list.");
            }

            if (Vect_get_num_primitives(&In, GV_LINE) > 0) {
                options.type->answers[num_types++] = G_store("line");
                G_debug(3, "Adding lines to export list.");
            }

            if (Vect_get_num_areas(&In) > 0) {
                options.type->answers[num_types++] = G_store("area");
                G_debug(3, "Adding areas to export list.");
            }

            /*  Faces and volumes:
               For now, volumes will just be exported as sets of faces.
             */
            if (Vect_get_num_primitives(&In, GV_FACE) > 0) {
                options.type->answers[num_types++] = G_store("face");
                G_debug(3, "Adding faces to export list.");
            }

            /* this check HAS TO FOLLOW RIGHT AFTER check for GV_FACE! */
            if (Vect_get_num_volumes(&In) > 0) {
                G_warning(_("Volumes will be exported as sets of faces"));
                if (num_types == 0) {
                    /* no other types yet? */
                    options.type->answers[num_types++] = G_store("volume");
                    G_debug(3, "Adding volumes to export list.");
                }
                else {
                    if (strcmp(options.type->answers[num_types - 1], "face") !=
                        0) {
                        /* only put faces on export list if that's not the case
                         * already */
                        options.type->answers[num_types++] = G_store("volume");
                        G_debug(3, "Adding volumes to export list.");
                    }
                }
            }

            if (num_types == 0) {
                G_warning(_(
                    "Unable to determine input map's vector feature type(s)."));
            }
        }
        field = Vect_get_field_number(&In, options.field->answer);
    }

    /* check output feature type */
    otype = Vect_option_to_types(options.type);
    ftype = Vect_option_to_types(options.otype);

    if (!options.layer->answer) {
        char xname[GNAME_MAX], xmapset[GMAPSET_MAX];

        if (G_name_is_fully_qualified(options.input->answer, xname, xmapset))
            options.layer->answer = G_store(xname);
        else
            options.layer->answer = G_store(options.input->answer);
    }

    if (ftype == GV_BOUNDARY) {
        if (!flags.multi->answer)
            wkbtype = wkbPolygon;
        else
            wkbtype = wkbMultiPolygon;
    }
    else if (otype & GV_POINTS) {
        if (!flags.multi->answer)
            wkbtype = wkbPoint;
        else
            wkbtype = wkbMultiPoint;
    }
    else if (otype & GV_LINES || ftype == GV_LINE) {
        if (!flags.multi->answer)
            wkbtype = wkbLineString;
        else
            wkbtype = wkbMultiLineString;
    }
    else if (otype & GV_AREA) {
        if (!flags.multi->answer)
            wkbtype = wkbPolygon;
        else
            wkbtype = wkbMultiPolygon;
    }
    else if (otype & (GV_FACE | GV_VOLUME)) {
        if (!flags.multi->answer)
            wkbtype = wkbPolygon25D;
        else
            wkbtype = wkbMultiPolygon25D;
    }

    if (((GV_POINTS & otype) && (GV_LINES & otype)) ||
        ((GV_POINTS & otype) && (GV_AREA & otype)) ||
        ((GV_POINTS & otype) && (GV_FACE & otype)) ||
        ((GV_POINTS & otype) && (GV_KERNEL & otype)) ||
        ((GV_POINTS & otype) && (GV_VOLUME & otype)) ||
        ((GV_LINES & otype) && (GV_AREA & otype)) ||
        ((GV_LINES & otype) && (GV_FACE & otype)) ||
        ((GV_LINES & otype) && (GV_KERNEL & otype)) ||
        ((GV_LINES & otype) && (GV_VOLUME & otype)) ||
        ((GV_KERNEL & otype) && (GV_POINTS & otype)) ||
        ((GV_KERNEL & otype) && (GV_LINES & otype)) ||
        ((GV_KERNEL & otype) && (GV_AREA & otype)) ||
        ((GV_KERNEL & otype) && (GV_FACE & otype)) ||
        ((GV_KERNEL & otype) && (GV_VOLUME & otype))) {
        G_warning(_("The combination of types is not supported"
                    " by all formats."));
        wkbtype = wkbUnknown;
    }

    /* fetch PROJ info */
    G_get_default_window(&cellhd);
    Ogr_projection = NULL;
    if (cellhd.proj != PROJECTION_XY) {
        struct Key_Value *projinfo, *projunits, *projepsg;

        projinfo = G_get_projinfo();
        projunits = G_get_projunits();
        projepsg = G_get_projepsg();

#if GDAL_VERSION_MAJOR >= 3 && PROJ_VERSION_MAJOR >= 6
        char *indef = NULL, *inwkt = NULL;

        if ((indef = G_get_projsrid())) {
            PJ *obj = NULL;

            if ((obj = proj_create(NULL, indef))) {
                inwkt = G_store(proj_as_wkt(NULL, obj, PJ_WKT2_LATEST, NULL));

                if (inwkt && !*inwkt) {
                    G_free(inwkt);
                    inwkt = NULL;
                }
            }
        }
        if (!inwkt) {
            inwkt = G_get_projwkt();
        }
        if (inwkt && *inwkt) {
            Ogr_projection = OSRNewSpatialReference(inwkt);
        }
#endif
        if (!Ogr_projection)
            Ogr_projection = GPJ_grass_to_osr2(projinfo, projunits, projepsg);

#if GDAL_VERSION_MAJOR >= 3 && PROJ_VERSION_MAJOR >= 6
        if (Ogr_projection) {
            /* convert bound CRS */
            PJ *obj = NULL;
            char *papszOptions[2];

            inwkt = NULL;

            papszOptions[0] = G_store("FORMAT=WKT2");
            papszOptions[1] = NULL;
            OSRExportToWktEx(Ogr_projection, &inwkt,
                             (const char **)papszOptions);
            G_free(papszOptions[0]);

            if ((obj = proj_create(NULL, inwkt))) {
                if (proj_get_type(obj) == PJ_TYPE_BOUND_CRS) {
                    PJ *source_crs;

                    G_debug(1, "found bound crs");
                    source_crs = proj_get_source_crs(NULL, obj);
                    if (source_crs) {
                        inwkt = G_store(proj_as_wkt(NULL, source_crs,
                                                    PJ_WKT2_LATEST, NULL));
                        if (inwkt && !*inwkt) {
                            G_free(inwkt);
                            inwkt = NULL;
                        }
                        proj_destroy(source_crs);
                        Ogr_projection = NULL;
                        if (inwkt) {
                            char *inwkttmp = inwkt;

                            OSRImportFromWkt(Ogr_projection, &inwkttmp);
                        }
                    }
                }
                proj_destroy(obj);
            }
            if (inwkt)
                CPLFree(inwkt);
        }
#endif

        if (Ogr_projection == NULL)
            G_fatal_error(_("Unable to create OGR spatial reference"));

        if (flags.esristyle->answer &&
            (strcmp(options.format->answer, "ESRI_Shapefile") == 0))
            OSRMorphToESRI(Ogr_projection);
    }

    dsn = NULL;
    if (options.dsn->answer)
        dsn = G_store(options.dsn->answer);

    /* create new OGR layer in datasource */
    if (flags.new->answer) {
        const char *name;

        name = options.layer->answer ? options.layer->answer
                                     : options.input->answer;

        create_ogr_layer(dsn, options.format->answer, name, wkbtype, papszDSCO,
                         papszLCO);

        G_message(_("OGR layer <%s> created in datasource <%s> (format '%s')"),
                  name, options.dsn->answer, options.format->answer);
        exit(EXIT_SUCCESS);
    }

    if (flags.cat->answer)
        donocat = 1;
    else
        donocat = 0;

    if ((GV_AREA & otype) && Vect_get_num_islands(&In) > 0 && flags.cat->answer)
        G_warning(_("The map contains islands. With the -c flag, "
                    "islands will appear as filled areas, not holes in the "
                    "output map."));

    /* check what users wants to export and what's present in the map */
    if (Vect_get_num_primitives(&In, GV_POINT) > 0 && !(otype & GV_POINTS))
        G_warning(n_("%d point found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     "%d points found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     Vect_get_num_primitives(&In, GV_POINT)),
                  Vect_get_num_primitives(&In, GV_POINT));

    if (Vect_get_num_primitives(&In, GV_LINE) > 0 && !(otype & GV_LINES))
        G_warning(n_("%d line found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     "%d line(s) found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     Vect_get_num_primitives(&In, GV_LINE)),
                  Vect_get_num_primitives(&In, GV_LINE));

    if (Vect_get_num_primitives(&In, GV_BOUNDARY) > 0 &&
        !(otype & GV_BOUNDARY) && !(otype & GV_AREA))
        G_warning(n_("%d boundary found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     "%d boundaries found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     Vect_get_num_primitives(&In, GV_BOUNDARY)),
                  Vect_get_num_primitives(&In, GV_BOUNDARY));

    if (Vect_get_num_primitives(&In, GV_CENTROID) > 0 &&
        !(otype & GV_CENTROID) && !(otype & GV_AREA))
        G_warning(n_("%d centroid found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     "%d centroids found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     Vect_get_num_primitives(&In, GV_CENTROID)),
                  Vect_get_num_primitives(&In, GV_CENTROID));

    if (Vect_get_num_areas(&In) > 0 && !(otype & GV_AREA))
        G_warning(n_("%d area found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     "%d areas found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     Vect_get_num_areas(&In)),
                  Vect_get_num_areas(&In));

    if (Vect_get_num_primitives(&In, GV_FACE) > 0 && !(otype & GV_FACE))
        G_warning(n_("%d face found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     "%d faces found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     Vect_get_num_primitives(&In, GV_FACE)),
                  Vect_get_num_primitives(&In, GV_FACE));

    if (Vect_get_num_volumes(&In) > 0 && !(otype & GV_VOLUME))
        G_warning(n_("%d volume found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     "%d volumes found, but not requested to be exported. "
                     "Verify 'type' parameter.",
                     Vect_get_num_volumes(&In)),
                  Vect_get_num_volumes(&In));

    /* warn and eventually abort if there is nothing to be exported */
    num_to_export = 0;
    if (Vect_get_num_primitives(&In, GV_POINT) < 1 && (otype & GV_POINTS)) {
        G_warning(_("No points found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_POINT)
            num_to_export += Vect_get_num_primitives(&In, GV_POINT);
    }

    if (Vect_get_num_primitives(&In, GV_LINE) < 1 && (otype & GV_LINE)) {
        G_warning(_("No lines found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_LINE)
            num_to_export += Vect_get_num_primitives(&In, GV_LINE);
    }

    if (Vect_get_num_primitives(&In, GV_BOUNDARY) < 1 &&
        (otype & GV_BOUNDARY)) {
        G_warning(_("No boundaries found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_BOUNDARY)
            num_to_export += Vect_get_num_primitives(&In, GV_BOUNDARY);
    }

    if (Vect_get_num_areas(&In) < 1 && (otype & GV_AREA)) {
        G_warning(_("No areas found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_AREA)
            num_to_export += Vect_get_num_areas(&In);
    }

    if (Vect_get_num_primitives(&In, GV_CENTROID) < 1 &&
        (otype & GV_CENTROID)) {
        G_warning(_("No centroids found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_CENTROID)
            num_to_export += Vect_get_num_primitives(&In, GV_CENTROID);
    }

    if (Vect_get_num_primitives(&In, GV_FACE) < 1 && (otype & GV_FACE)) {
        G_warning(_("No faces found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_FACE)
            num_to_export += Vect_get_num_primitives(&In, GV_FACE);
    }

    if (Vect_get_num_primitives(&In, GV_KERNEL) < 1 && (otype & GV_KERNEL)) {
        G_warning(_("No kernels found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_KERNEL)
            num_to_export += Vect_get_num_primitives(&In, GV_KERNEL);
    }

    if (Vect_get_num_volumes(&In) < 1 && (otype & GV_VOLUME)) {
        G_warning(_("No volumes found, but requested to be exported. "
                    "Will skip this feature type."));
    }
    else {
        if (otype & GV_VOLUME)
            num_to_export += Vect_get_num_volumes(&In);
    }

    G_debug(1, "Requested to export %d features", num_to_export);

    /* Open OGR DSN */
    G_debug(2, "driver count = %d", GDALGetDriverCount());
    drn = -1;
    for (i = 0; i < GDALGetDriverCount(); i++) {
        hDriver = GDALGetDriver(i);
        G_debug(2, "driver %d : %s", i, GDALGetDriverShortName(hDriver));
        /* chg white space to underscore in OGR driver names */
        sprintf(buf, "%s", GDALGetDriverShortName(hDriver));
        G_strchg(buf, ' ', '_');
        if (strcmp(buf, options.format->answer) == 0) {
            drn = i;
            G_debug(2, " -> driver = %d", drn);
        }
    }
    if (drn == -1)
        G_fatal_error(_("OGR driver <%s> not found"), options.format->answer);
    hDriver = GDALGetDriver(drn);

    if (flags.append->answer) {
        G_debug(1, "Append to OGR layer");
        hDS =
            GDALOpenEx(dsn, GDAL_OF_VECTOR | GDAL_OF_UPDATE, NULL, NULL, NULL);

        if (hDS == NULL) {
            G_debug(1, "Create OGR data source");
            hDS = GDALCreate(hDriver, dsn, 0, 0, 0, GDT_Unknown, papszDSCO);
        }
    }
    else {
        if (flags.update->answer) {
            G_debug(1, "Update OGR data source");
            hDS = GDALOpenEx(dsn, GDAL_OF_VECTOR | GDAL_OF_UPDATE, NULL, NULL,
                             NULL);
        }
        else {
            G_debug(1, "Create OGR data source");
            hDS = GDALCreate(hDriver, dsn, 0, 0, 0, GDT_Unknown, papszDSCO);
        }
    }

    CSLDestroy(papszDSCO);
    if (hDS == NULL)
        G_fatal_error(_("Unable to open OGR data source '%s'"),
                      options.dsn->answer);

    /* check if OGR layer exists */
    overwrite = G_check_overwrite(argc, argv);
    found = FALSE;
    for (i = 0; i < GDALDatasetGetLayerCount(hDS); i++) {
        Ogr_layer = GDALDatasetGetLayer(hDS, i);
        Ogr_field = OGR_L_GetLayerDefn(Ogr_layer);
        if (G_strcasecmp(OGR_FD_GetName(Ogr_field), options.layer->answer))
            continue;

        found = TRUE;
        if (!overwrite && !flags.append->answer) {
            G_fatal_error(
                _("Layer <%s> already exists in OGR data source '%s'"),
                options.layer->answer, options.dsn->answer);
        }
        else if (overwrite && !flags.append->answer) {
            G_warning(
                _("OGR layer <%s> already exists and will be overwritten"),
                options.layer->answer);
            GDALDatasetDeleteLayer(hDS, i);
            break;
        }
    }

    if (flags.append->answer && !found) {
        G_warning(_("OGR layer <%s> doesn't exists, "
                    "creating new OGR layer instead"),
                  options.layer->answer);
        flags.append->answer = FALSE;
    }

    /* Automatically append driver options for 3D output to layer
       creation options if '2' is not given. */
    if (!flags.force2d->answer && Vect_is_3d(&In) &&
        strcmp(options.format->answer, "ESRI_Shapefile") == 0) {
        /* find right option */
        char shape_geom[20];

        if ((otype & GV_POINTS) || (otype & GV_KERNEL))
            sprintf(shape_geom, "POINTZ");
        if ((otype & GV_LINES))
            sprintf(shape_geom, "ARCZ");
        if ((otype & GV_AREA) || (otype & GV_FACE))
            sprintf(shape_geom, "POLYGONZ");
        /* check if the right LCO is already present */
        const char *shpt;

        shpt = CSLFetchNameValue(papszLCO, "SHPT");
        if ((!shpt)) {
            /* Not set at all? Good! */
            papszLCO = CSLSetNameValue(papszLCO, "SHPT", shape_geom);
        }
        else {
            if (strcmp(shpt, shape_geom) != 0) {
                /* Set but to a different value? Override! */
                G_warning(_("Overriding existing user-defined 'SHPT=' LCO."));
            }
            /* Set correct LCO for this geometry type */
            papszLCO = CSLSetNameValue(papszLCO, "SHPT", shape_geom);
        }
    }

    /* check if the map is 3d */
    if (Vect_is_3d(&In)) {
        /* specific check for ESRI ShapeFile */
        if (strcmp(options.format->answer, "ESRI_Shapefile") == 0) {
            const char *shpt;

            shpt = CSLFetchNameValue(papszLCO, "SHPT");
            if (!shpt || shpt[strlen(shpt) - 1] != 'Z') {
                G_warning(_("Vector map <%s> is 3D. "
                            "Use format specific layer creation options SHPT "
                            "(parameter 'lco') "
                            "to export in 3D rather than 2D (default)"),
                          options.input->answer);
            }
        }
        /* specific check for PostgreSQL */
        else if (strcmp(options.format->answer, "PostgreSQL") == 0) {
            const char *dim;

            dim = CSLFetchNameValue(papszLCO, "DIM");
            if (!dim || strcmp(dim, "3") != 0) {
                G_warning(_("Vector map <%s> is 3D. "
                            "Use format specific layer creation options DIM "
                            "(parameter 'lco') "
                            "to export in 3D rather than 2D (default)."),
                          options.input->answer);
            }
        }
        else {
            G_warning(_("Vector map <%s> is 3D. "
                        "Use format specific layer creation options (parameter "
                        "'lco') "
                        "to export in 3D rather than 2D (default)."),
                      options.input->answer);
        }
    }

    G_debug(1, "Create OGR layer");
    if (flags.append->answer)
        Ogr_layer = GDALDatasetGetLayerByName(hDS, options.layer->answer);
    else
        Ogr_layer = GDALDatasetCreateLayer(hDS, options.layer->answer,
                                           Ogr_projection, wkbtype, papszLCO);

    CSLDestroy(papszLCO);
    if (Ogr_layer == NULL) {
        if (flags.append->answer)
            G_fatal_error(_("OGR layer <%s> not found"), options.layer->answer);
        else
            G_fatal_error(_("Unable to create OGR layer"));
    }

    db_init_string(&dbstring);

    /* Vector attributes -> OGR fields */
    if (field > 0) {
        G_debug(1, "Create attribute table");
        doatt = 1; /* do attributes */
        Fi = Vect_get_field(&In, field);
        if (Fi == NULL) {
            char create_field = TRUE;

            G_warning(_("No attribute table found -> using only category "
                        "numbers as attributes"));
            /* if we have no more than a 'cat' column, then that has to
               be exported in any case */
            if (flags.nocat->answer) {
                G_warning(_("Exporting 'cat' anyway, as it is the only "
                            "attribute table field"));
                flags.nocat->answer = FALSE;
            }

            if (flags.append->answer) {
                Ogr_field = OGR_L_GetLayerDefn(Ogr_layer);
                if (OGR_FD_GetFieldIndex(Ogr_field, GV_KEY_COLUMN) > -1)
                    create_field = FALSE;
                else
                    G_warning(_("New attribute column <%s> added to the table"),
                              GV_KEY_COLUMN);
            }

            if (create_field) {
                Ogr_field = OGR_Fld_Create(GV_KEY_COLUMN, OFTInteger);
                if (OGR_L_CreateField(Ogr_layer, Ogr_field, 0) != OGRERR_NONE)
                    G_fatal_error(_("Unable to create column <%s>"),
                                  GV_KEY_COLUMN);
                OGR_Fld_Destroy(Ogr_field);
            }

            doatt = 0;
        }
        else {
            Driver = db_start_driver_open_database(Fi->driver, Fi->database);
            if (!Driver)
                G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
                              Fi->database, Fi->driver);

            db_set_string(&dbstring, Fi->table);
            if (db_describe_table(Driver, &dbstring, &Table) != DB_OK)
                G_fatal_error(_("Unable to describe table <%s>"), Fi->table);

            ncol = db_get_table_number_of_columns(Table);
            G_debug(2, "ncol = %d", ncol);
            colctype = G_malloc(ncol * sizeof(int));
            colname = G_malloc(ncol * sizeof(char *));
            keycol = -1;
            for (i = 0; i < ncol; i++) {
                Column = db_get_table_column(Table, i);
                colname[i] = G_store(db_get_column_name(Column));
                colsqltype = db_get_column_sqltype(Column);
                colctype[i] = db_sqltype_to_Ctype(colsqltype);
                colwidth = db_get_column_length(Column);
                G_debug(3, "col %d: %s sqltype=%d ctype=%d width=%d", i,
                        colname[i], colsqltype, colctype[i], colwidth);

                switch (colctype[i]) {
                case DB_C_TYPE_INT:
                    ogr_ftype = OFTInteger;
                    break;
                case DB_C_TYPE_DOUBLE:
                    ogr_ftype = OFTReal;
                    break;
                case DB_C_TYPE_STRING:
                    ogr_ftype = OFTString;
                    break;
                case DB_C_TYPE_DATETIME:
                    ogr_ftype = OFTDateTime;
                    break;
                }
                G_debug(2, "ogr_ftype = %d", ogr_ftype);

                strcpy(key1, Fi->key);
                G_tolcase(key1);
                strcpy(key2, colname[i]);
                G_tolcase(key2);
                if (strcmp(key1, key2) == 0)
                    keycol = i;
                G_debug(2, "%s x %s -> %s x %s -> keycol = %d", Fi->key,
                        colname[i], key1, key2, keycol);

                if (flags.nocat->answer && strcmp(Fi->key, colname[i]) == 0)
                    /* skip export of 'cat' field */
                    continue;

                if (flags.append->answer) {
                    Ogr_field = OGR_L_GetLayerDefn(Ogr_layer);
                    if (OGR_FD_GetFieldIndex(Ogr_field, colname[i]) > -1)
                        /* skip existing fields */
                        continue;
                    else
                        G_warning(
                            _("New attribute column <%s> added to the table"),
                            colname[i]);
                }

                Ogr_field = OGR_Fld_Create(colname[i], ogr_ftype);
                if (ogr_ftype == OFTString && colwidth > 0)
                    OGR_Fld_SetWidth(Ogr_field, colwidth);
                if (OGR_L_CreateField(Ogr_layer, Ogr_field, 0) != OGRERR_NONE)
                    G_fatal_error(_("Unable to create column <%s>"),
                                  colname[i]);

                OGR_Fld_Destroy(Ogr_field);
            }
            if (keycol == -1)
                G_fatal_error(_("Key column <%s> not found"), Fi->key);
        }
    }

    Ogr_featuredefn = OGR_L_GetLayerDefn(Ogr_layer);

    n_feat = n_nocat = n_noatt = 0;

    if (OGR_L_TestCapability(Ogr_layer, OLCTransactions)) {
        if (OGR_L_StartTransaction(Ogr_layer) != OGRERR_NONE) {
            G_warning(_("Unable to start OGR transaction"));
        }
    }

    /* export polygons oriented according to OGC simple features standard 1.2.1
     * outer rings are oriented counter-clockwise (CCW)
     * inner rings are oriented clockwise (CW) */
    outer_ring_ccw = 1;
    /* some formats expect outer rings to be CW and inner rings to be CCW:
     * ESRI Shapefile, PGeo, FileGDB, OpenFileGDB (all ESRI) */
    if (strcmp(options.format->answer, "ESRI_Shapefile") == 0 ||
        strcmp(options.format->answer, "PGeo") == 0 ||
        strcmp(options.format->answer, "FileGDB") == 0 ||
        strcmp(options.format->answer, "OpenFileGDB") == 0) {
        outer_ring_ccw = 0;
    }
    G_debug(1, "Format \"%s\", outer ring %s", options.format->answer,
            (outer_ring_ccw ? "CCW" : "CW"));

    /* Lines (run always to count features of different type) */
    if (otype & (GV_POINTS | GV_LINES | GV_KERNEL | GV_FACE)) {
        G_message(n_("Exporting %d feature...", "Exporting %d features...",
                     Vect_get_num_primitives(&In, otype)),
                  Vect_get_num_primitives(&In, otype));

        n_feat += export_lines(
            &In, field, otype, flags.multi->answer ? TRUE : FALSE, donocat,
            ftype == GV_BOUNDARY ? TRUE : FALSE, Ogr_featuredefn, Ogr_layer, Fi,
            Driver, ncol, colctype, colname, doatt,
            flags.nocat->answer ? TRUE : FALSE, &n_noatt, &n_nocat);
    }

    /* Areas (run always to count features of different type) */
    if (Vect_get_num_areas(&In) > 0 && (otype & GV_AREA)) {
        G_message(n_("Exporting %d area (may take some time)...",
                     "Exporting %d areas (may take some time)...",
                     Vect_get_num_areas(&In)),
                  Vect_get_num_areas(&In));

        n_feat += export_areas(&In, field, flags.multi->answer ? TRUE : FALSE,
                               donocat, Ogr_featuredefn, Ogr_layer, Fi, Driver,
                               ncol, colctype, colname, doatt,
                               flags.nocat->answer ? TRUE : FALSE, &n_noatt,
                               &n_nocat, outer_ring_ccw);
    }

    /*
       TODO: Volumes. Do not export kernels here, that's already done.
       We do need to worry about holes, though. NOTE: We can probably
       just merge this with faces export function. Except for GRASS,
       which output format would know the difference?
     */
    if (Vect_get_num_volumes(&In) > 0 && (otype & GV_VOLUME)) {
        G_message(n_("Exporting %d volume...", "Exporting %d volumes...",
                     Vect_get_num_volumes(&In)),
                  Vect_get_num_volumes(&In));
        G_warning(_("Export of volumes not implemented yet. Skipping."));
    }

    if (OGR_L_TestCapability(Ogr_layer, OLCTransactions)) {
        if (OGR_L_CommitTransaction(Ogr_layer) != OGRERR_NONE) {
            G_warning(_("Unable to commit OGR transaction"));
        }
    }

    GDALClose(hDS);

    Vect_close(&In);

    if (doatt) {
        db_close_database(Driver);
        db_shutdown_driver(Driver);
    }

    /* Summary */
    if (n_noatt > 0)
        G_important_message(n_("%d feature without attributes was written",
                               "%d features without attributes were written",
                               n_noatt),
                            n_noatt);

    if (n_nocat > 0) {
        if (donocat)
            G_important_message(n_("%d feature without category was written",
                                   "%d features without category were written",
                                   n_nocat),
                                n_nocat);
        else
            G_warning(n_("%d feature without category was skipped. "
                         "Features without category are written only when -%c "
                         "flag is given.",
                         "%d features without category were skipped. "
                         "Features without category are written only when -%c "
                         "flag is given.",
                         n_nocat),
                      n_nocat, flags.cat->key);
    }

    /* Enable this? May be confusing that for area type are not
     * reported all boundaries/centroids. OTOH why should be
     * reported? */
    /*
       if (((otype & GV_POINTS) || (otype & GV_LINES)) && fskip > 0)
       G_warning ("%d features of different type skip", fskip);
     */

    if (n_feat < 1)
        G_warning(_("Output layer is empty, no features written"));
    G_done_msg(n_("%d feature (%s type) written to <%s> (%s format).",
                  "%d features (%s type) written to <%s> (%s format).", n_feat),
               n_feat, OGRGeometryTypeToName(wkbtype), options.layer->answer,
               options.format->answer);

    exit(EXIT_SUCCESS);
}
