
/**********************************************************
 *
 * MODULE:       v.drape
 * 
 * AUTHOR(S):    Radim Blazek, Dylan Beaudette
 *               Maris Nartiss - WHERE support and raster NULL support
 * PURPOSE:      Convert 2D vector to 3D vector by sampling of elevation raster.
 *               
 * COPYRIGHT:    (C) 2005 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.
 * 
 **********************************************************/


/** Doxygen Style Comments
\file main.c
\brief v.drape module for converting 2D vectors into 3D vectors by means of sampling an elevation raster.
 
\author Radim Blazek
\author Dylan Beaudette
\date 2005.09.20
 
\todo add support for areas
*/

#include <stdlib.h>
#include <grass/gis.h>
#include <grass/Vect.h>
#include <grass/dbmi.h>
#include <grass/glocale.h>

/* Samples raster map */
int sample_raster(const int ltype, int fdrast, struct Cell_head window,
		  struct line_pnts *Points, const INTERP_TYPE method,
		  const double scale, const struct Option *null_opt,
		  const double null_val)
{
    double estimated_elevation;
    int j;

    /* adjust flow based on specific type of line */
    switch (ltype) {
	/* points (at least 1 vertex) */
    case GV_POINT:
    case GV_CENTROID:
    case GV_KERNEL:
	/* sample raster at this point, and update the z-coordinate
	 * (note that input vector should not be 3D!)
	 */
	estimated_elevation = scale * G_get_raster_sample(fdrast,
							  &window,
							  NULL,
							  Points->
							  y[0],
							  Points->
							  x[0], 0, method);
	/* Elevation value has to be meaningfull */
	if (G_is_d_null_value(&estimated_elevation)) {
	    if (null_opt->answer) {
		estimated_elevation = null_val;
	    }
	    else {
		return 0;
	    }
	}
	/* update the elevation value for each data point */
	Points->z[0] = estimated_elevation;
	break;
	/* standard lines (at least 2 vertexes) */
    case GV_LINE:
    case GV_BOUNDARY:
	if (Points->n_points < 2)
	    break;		/* At least 2 points */

	/* loop through each point in a line */
	for (j = 0; j < Points->n_points; j++) {
	    /* sample raster at this point, and update the z-coordinate (note that input vector should not be 3D!) */
	    estimated_elevation = scale * G_get_raster_sample(fdrast,
							      &window,
							      NULL,
							      Points->
							      y[j],
							      Points->
							      x[j], 0,
							      method);

	    if (G_is_d_null_value(&estimated_elevation)) {
		if (null_opt->answer) {
		    estimated_elevation = null_val;
		}
		else {
		    return 0;
		}
	    }
	    /* update the elevation value for each data point */
	    Points->z[j] = estimated_elevation;
	}			/* end looping through point in a line */
	break;

	/* lines with at least 3 vertexes */
    case GV_FACE:
	if (Points->n_points < 3)
	    break;		/* At least 3 points */

	/* loop through each point in a line */
	for (j = 0; j < Points->n_points; j++) {
	    /* sample raster at this point, and update the z-coordinate (note that input vector should not be 3D!) */
	    estimated_elevation = scale * G_get_raster_sample(fdrast,
							      &window,
							      NULL,
							      Points->
							      y[j],
							      Points->
							      x[j], 0,
							      method);

	    if (G_is_d_null_value(&estimated_elevation)) {
		if (null_opt->answer) {
		    estimated_elevation = null_val;
		}
		else {
		    return 0;
		}
	    }
	    /* update the elevation value for each data point */
	    Points->z[j] = estimated_elevation;
	}
	break;
    }				/* end line type switch */
    return 1;
}

int main(int argc, char *argv[])
{
    struct GModule *module;
    struct Option *in_opt, *out_opt, *type_opt, *rast_opt, *method_opt,
	*scale_opt, *where_opt, *layer_opt, *null_opt;

    struct Map_info In, Out;
    struct line_pnts *Points;
    struct line_cats *Cats;
    struct field_info *Fi;

    int line, nlines, otype, ltype, ncats =
	0, layer, i, c, *cats, field_index, id, out_num = 0, *new_cats;

    char *mapset;
    double scale, null_val;
    INTERP_TYPE method = UNKNOWN;
    int fdrast;			/* file descriptor for raster map is int */
    struct Cell_head window;

    dbDriver *driver;
    dbHandle handle;
    dbTable *table;
    dbString table_name, dbsql, valstr;

    G_gisinit(argv[0]);

    module = G_define_module();
    module->keywords = _("vector, geometry, sampling");
    module->description =
	_("Converts vector map to 3D by sampling of elevation raster map.");

    in_opt = G_define_standard_option(G_OPT_V_INPUT);

    type_opt = G_define_standard_option(G_OPT_V_TYPE);
    type_opt->options = "point,centroid,line,boundary,face,kernel";
    type_opt->answer = "point,centroid,line,boundary,face,kernel";

    /* raster sampling */
    rast_opt = G_define_standard_option(G_OPT_R_MAP);
    rast_opt->key = "rast";
    rast_opt->required = NO;
    rast_opt->description = _("Elevation raster map for height extraction");
    
    out_opt = G_define_standard_option(G_OPT_V_OUTPUT);

    method_opt = G_define_option();
    method_opt->key = "method";
    method_opt->type = TYPE_STRING;
    method_opt->required = NO;
    method_opt->multiple = NO;
    method_opt->options = "nearest,bilinear,cubic";
    method_opt->answer = "nearest";
    method_opt->descriptions = "nearest;nearest neighbor;"
	"bilinear;bilinear interpolation;"
	"cubic;cubic convolution interpolation;";
    method_opt->description = _("Sampling method");
    
    scale_opt = G_define_option();
    scale_opt->key = "scale";
    scale_opt->type = TYPE_DOUBLE;
    scale_opt->description = _("Scale sampled raster values");
    scale_opt->answer = "1.0";

    where_opt = G_define_standard_option(G_OPT_WHERE);

    layer_opt = G_define_standard_option(G_OPT_V_FIELD);
    layer_opt->answer = "1";
    layer_opt->description = _("Layer is only used for WHERE SQL statement");

    null_opt = G_define_option();
    null_opt->key = "null_value";
    null_opt->type = TYPE_DOUBLE;
    null_opt->label = _("Vector Z value for unknown height");
    null_opt->description =
	_("Will set Z to this value, if value from raster map can not be read");

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

    layer = atoi(layer_opt->answer);
    if (layer == 0)
	G_fatal_error(_("Layer 0 not supported"));

    if (null_opt->answer)
	null_val = atof(null_opt->answer);

    /* which interpolation method should we use */
    if (method_opt->answer[0] == 'b')
	method = BILINEAR;
    else if (method_opt->answer[0] == 'c')
	method = CUBIC;
    else {
	method = NEAREST;
    }

    /* setup the region */
    G_get_window(&window);

    /* used to scale sampled raster values */
    scale = atof(scale_opt->answer);

    /* Check output type */
    otype = Vect_option_to_types(type_opt);

    /* check for the elev raster, and check for error condition */
    if ((mapset = G_find_cell(rast_opt->answer, "")) == NULL) {
	G_fatal_error(_("Raster map <%s> not found"), rast_opt->answer);
    }

    /* open the elev raster, and check for error condition */
    if ((fdrast = G_open_cell_old(rast_opt->answer, mapset)) < 0) {
	G_fatal_error(_("Unable to open raster map <%s>"), rast_opt->answer);
    }

    Vect_set_open_level(2);

    /* check input/output vector maps */
    Vect_check_input_output_name(in_opt->answer, out_opt->answer,
				 GV_FATAL_EXIT);

    mapset = G_find_vector2(in_opt->answer, "");
    if (!mapset) {
	G_fatal_error(_("Vector map <%s> not found"), in_opt->answer);
    }

    Vect_open_old(&In, in_opt->answer, mapset);

    if (where_opt->answer) {
	/* Let's get vector layers db connections information */
	Fi = Vect_get_field(&In, layer);
	if (!Fi) {
	    Vect_close(&In);
	    G_fatal_error(_("Database connection not defined for layer %d"),
			  layer);
	}
	G_debug(1,
		"Field number:%d; Name:<%s>; Driver:<%s>; Database:<%s>; Table:<%s>; Key:<%s>",
		Fi->number, Fi->name, Fi->driver, Fi->database, Fi->table,
		Fi->key);
	/* Prepeare strings for use in db_* calls */
	db_init_string(&dbsql);
	db_init_string(&valstr);
	db_init_string(&table_name);
	db_init_handle(&handle);

	/* Prepearing database for use */
	driver = db_start_driver(Fi->driver);
	if (driver == NULL) {
	    Vect_close(&In);
	    G_fatal_error(_("Unable to start driver <%s>"), Fi->driver);
	}
	db_set_handle(&handle, Fi->database, NULL);
	if (db_open_database(driver, &handle) != DB_OK) {
	    Vect_close(&In);
	    G_fatal_error(_("Unable to open database <%s> by driver <%s>"),
			  Fi->database, driver);
	}
	db_set_string(&table_name, Fi->table);
	if (db_describe_table(driver, &table_name, &table) != DB_OK) {
	    Vect_close(&In);
	    G_fatal_error(_("Unable to describe table <%s>"), Fi->table);
	}
	ncats =
	    db_select_int(driver, Fi->table, Fi->key, where_opt->answer,
			  &cats);
	if (ncats < 1)
	    G_fatal_error(_("No features match Your query"));
	G_debug(3, "Number of features matching query: %d", ncats);
    }

    Points = Vect_new_line_struct();
    Cats = Vect_new_cats_struct();

    /* line types */
    if ((otype &
	 (GV_POINTS | GV_LINES | GV_BOUNDARY | GV_CENTROID | GV_FACE |
	  GV_KERNEL))) {

	/* process all lines matching WHERE statement */
	if (where_opt->answer) {
	    field_index = Vect_cidx_get_field_index(&In, layer);
	    for (i = 0; i < ncats; i++) {
		G_percent(i, ncats, 2);
                
		c = Vect_cidx_find_next(&In, field_index, cats[i],
					otype, 0, &ltype, &id);
		while (c >= 0) {
		    c++;
		    if (ltype & otype) {
			if (Vect_read_line(&In, Points, Cats, id) == ltype)
			    if (sample_raster
				(ltype, fdrast, window, Points, method,
				 scale, null_opt, null_val)) {
				
                                /* Open new file only if there is something to write in */
				if (out_num < 1) {
				    if (0 >
					Vect_open_new(&Out, out_opt->answer,
						      WITH_Z)) {
					Vect_close(&In);
					G_fatal_error(_("Unable to create vector map <%s>"),
						      out_opt->answer);
				    }
				    Vect_copy_head_data(&In, &Out);
				    Vect_hist_copy(&In, &Out);
				    Vect_hist_command(&Out);
				}
                                
				Vect_write_line(&Out, ltype, Points, Cats);
				out_num++;
			    }
		    }
		    else
			G_fatal_error
			    ("Error in Vect_cidx_find_next function! Report a bug.");
		    c = Vect_cidx_find_next(&In, field_index, cats[i],
					    otype, c, &ltype, &id);
		}
	    }
	}
	else {
	    /* loop through each line in the dataset */
	    nlines = Vect_get_num_lines(&In);

	    for (line = 1; line <= nlines; line++) {

		/* progress feedback */
		G_percent(line, nlines, 2);

		/* get the line type */
		ltype = Vect_read_line(&In, Points, Cats, line);

		/* write the new line file, with the updated Points struct */
		if (sample_raster
		    (ltype, fdrast, window, Points, method, scale, null_opt,
		     null_val)) {
		    
                    /* Open new file only if there is something to write in */
		    if (out_num < 1) {
			if (0 > Vect_open_new(&Out, out_opt->answer, WITH_Z)) {
			    Vect_close(&In);
			    G_fatal_error(_("Unable to create vector map <%s>"),
					  out_opt->answer);
			}
			Vect_copy_head_data(&In, &Out);
			Vect_hist_copy(&In, &Out);
			Vect_hist_command(&Out);
		    }
                    
		    Vect_write_line(&Out, ltype, Points, Cats);
		    out_num++;
		}
	    }			/* end looping thru lines */
	}

    }				/* end working on type=lines */

    /* close elevation raster: */
    G_close_cell(fdrast);
    
    /* build topology for output vector */
    if (out_num > 0) {
	Vect_build(&Out);
        
        /* Now let's move attribute data from all old map layers to new map */
        for (i = 0; i < Out.plus.n_cidx; i++) {
            new_cats = (int *)G_malloc(Out.plus.cidx[i].n_cats * sizeof(int));
            if (!new_cats) {
                G_warning(_("Due to error attribute data to new map are not transferred"));
                break;
            }
            /* Vect_copy_table_by_cats does not accept Map.plus.cidx[].cat array as input.
            	Thus we construct new array of cats. */
            for (c = 0; c < Out.plus.cidx[i].n_cats; c++) {
                new_cats[c] = Out.plus.cidx[i].cat[c][0];
            }
            if (0 > Vect_copy_table_by_cats(&In, &Out, In.plus.cidx[i].field, Out.plus.cidx[i].field,
                    NULL, otype, new_cats, Out.plus.cidx[i].n_cats))
                    G_warning(_("Due to error attribute data to new map are not transferred"));
        }
	/* close output vector */
	Vect_close(&Out);
    }
    else {
        /* close input vector */
        Vect_close(&In);
	G_warning(_("No features drapped. Check Your computational region and input raster map."));
	exit(EXIT_FAILURE);
    }
    /* close input vector */
    Vect_close(&In);
    
    exit(EXIT_SUCCESS);
}
