
/****************************************************************************
 *
 * MODULE:       v.voronoi
 * AUTHOR(S):    James McCauley <mccauley ecn.purdue.edu>, s.voronoi, based
 *                     on netlib code (see README) (original contributor)
 *               Andrea Aime <aaime libero.it>
 *               Radim Blazek <radim.blazek gmail.com> (GRASS 5.1 v.voronoi) 
 *               Glynn Clements <glynn gclements.plus.com>,  
 *               Markus Neteler <neteler itc.it>,
 *               Markus Metz
 * PURPOSE:      produce a Voronoi diagram using vector points
 * COPYRIGHT:    (C) 1993-2006, 2001, 2013 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.
 *
 *****************************************************************************/

/*-s.voronoi
**
** Author: James Darrell McCauley (mccauley@ecn.purdue.edu)
**         USDA Fellow
**         Department of Agricultural Engineering
**         Purdue University
**         West Lafayette, Indiana 47907-1146 USA
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted. This
** software is provided "as is" without express or implied warranty.
**
** Modification History:
** 06 Feb 93 - James Darrell McCauley <mccauley@ecn.purdue.edu> pieced
**             this together from stuff he found on netlib (see the manpage).
**/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <grass/gis.h>
#include <grass/dbmi.h>
#include <grass/vector.h>
#include <grass/glocale.h>
#include "sw_defs.h"
#include "defs.h"

typedef struct
{
    double x, y;
} COOR;



int cmp(const void *a, const void *b)
{
    COOR *ca = (COOR *) a;
    COOR *cb = (COOR *) b;
    double ma, mb;

    /* calculate measure */
    ma = mb = 0.0;

    if (fabs(ca->y - Box.S) < GRASS_EPSILON) {	/* bottom */
	ma = ca->x - Box.W;
    }
    else if (fabs(ca->x - Box.E) < GRASS_EPSILON) {	/* right */
	ma = (Box.E - Box.W) + (ca->y - Box.S);
    }
    else if (fabs(ca->y - Box.N) < GRASS_EPSILON) {	/* top */
	ma = (Box.E - Box.W) + (Box.N - Box.S) + (Box.E - ca->x);
    }
    else {			/* left */
	ma = 2 * (Box.E - Box.W) + (Box.N - Box.S) + (Box.N - ca->y);
    }


    if (fabs(cb->y - Box.S) < GRASS_EPSILON) {	/* bottom */
	mb = cb->x - Box.W;
    }
    else if (fabs(cb->x - Box.E) < GRASS_EPSILON) {	/* right */
	mb = (Box.E - Box.W) + (cb->y - Box.S);
    }
    else if (fabs(cb->y - Box.N) < GRASS_EPSILON) {	/* top */
	mb = (Box.E - Box.W) + (Box.N - Box.S) + (Box.E - cb->x);
    }
    else {			/* left */
	mb = 2 * (Box.E - Box.W) + (Box.N - Box.S) + (Box.N - cb->y);
    }

    if (ma < mb)
	return -1;
    if (ma > mb)
	return 1;
    return 0;
}

int main(int argc, char **argv)
{
    int i;
    int **cats, *ncats, nfields, *fields;
    struct {
	struct Option *in, *out, *field, *smooth, *thin;
    } opt;
    struct {
	struct Flag *line, *table, *area, *skeleton;
    } flag;
    struct GModule *module;
    struct line_pnts *Points;
    struct line_cats *Cats;
    int node, nnodes;
    COOR *coor;
    int verbose;
    int ncoor, acoor;
    int line, nlines, type, ctype;
    double thresh;

    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("vector"));
    G_add_keyword(_("geometry"));
    G_add_keyword(_("triangulation"));
    G_add_keyword(_("skeleton"));
    module->description = _("Creates a Voronoi diagram constrained to the extents of the current region from "
			    "an input vector map containing points or centroids.");

    opt.in = G_define_standard_option(G_OPT_V_INPUT);
    opt.in->label = _("Name of input vector map");
    
    opt.field = G_define_standard_option(G_OPT_V_FIELD_ALL);
    
    opt.out = G_define_standard_option(G_OPT_V_OUTPUT);

    opt.smooth = G_define_option();
    opt.smooth->type = TYPE_DOUBLE;
    opt.smooth->key = "smoothness";
    opt.smooth->answer = "0.25";
    opt.smooth->label = _("Factor for output smoothness");
    opt.smooth->description = _("Applies to input areas only. "
                                "Smaller values produce smoother output but can cause numerical instability.");

    opt.thin = G_define_option();
    opt.thin->type = TYPE_DOUBLE;
    opt.thin->key = "thin";
    opt.thin->answer = "-1";
    opt.thin->label = _("Maximum dangle length of skeletons");
    opt.thin->description = _("Applies only to skeleton extraction. "
                                "Default = -1 will extract the center line.");

    flag.area = G_define_flag();
    flag.area->key = 'a';
    flag.area->description =
	_("Create Voronoi diagram for input areas");

    flag.skeleton = G_define_flag();
    flag.skeleton->key = 's';
    flag.skeleton->description =
	_("Extract skeletons for input areas");

    flag.line = G_define_flag();
    flag.line->key = 'l';
    flag.line->description =
	_("Output tessellation as a graph (lines), not areas");

    flag.table = G_define_standard_flag(G_FLG_V_TABLE);
    
    if (G_parser(argc, argv))
	exit(EXIT_FAILURE);
    
    if (flag.line->answer)
	Type = GV_LINE;
    else
	Type = GV_BOUNDARY;

    in_area = flag.area->answer;
    segf = atof(opt.smooth->answer);
    if (segf < GRASS_EPSILON) {
	segf = 0.25;
	G_warning(_("Option '%s' is too small, set to %g"), opt.smooth->key, segf);
    }
    thresh = atof(opt.thin->answer);
    
    skeleton = flag.skeleton->answer;
    if (skeleton)
	Type = GV_LINE;

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

    /* open files */
    Vect_set_open_level(2);
    if (Vect_open_old2(&In, opt.in->answer, "", opt.field->answer) < 0)
	G_fatal_error(_("Unable to open vector map <%s>"), opt.in->answer);

    Field = Vect_get_field_number(&In, opt.field->answer);
    
    /* initialize working region */
    G_get_window(&Window);
    Vect_region_box(&Window, &Box);
    Box.T = 0.5;
    Box.B = -0.5;
    xcenter = (Box.W + Box.E) / 2.0;
    ycenter = (Box.S + Box.N) / 2.0;

    freeinit(&sfl, sizeof(struct Site));

    G_message(_("Reading features..."));
    if (in_area || skeleton)
	readbounds();
    else
	readsites();

    if (in_area) {
	if (Vect_open_tmp_new(&Out, NULL, 0) < 0)
	    G_fatal_error(_("Unable to create temporary vector map"));

	if (Vect_open_new(&Out2, opt.out->answer, 0) < 0)
	    G_fatal_error(_("Unable to create vector map <%s>"), opt.out->answer);
    }
    else {
	if (Vect_open_new(&Out, opt.out->answer, 0) < 0)
	    G_fatal_error(_("Unable to create vector map <%s>"), opt.out->answer);
    }

    Vect_hist_copy(&In, &Out);
    Vect_hist_command(&Out);
    
    siteidx = 0;
    geominit();

    plot = 0;
    debug = 0;

    G_message(n_("Voronoi triangulation for %d point...",
                 "Voronoi triangulation for %d points...",
                 nsites), nsites);
    voronoi(nextone);
    G_message(_("Writing edges..."));
    vo_write();

    verbose = G_verbose();
    G_set_verbose(0);
    Vect_build_partial(&Out, GV_BUILD_BASE);
    G_set_verbose(verbose);

    if (skeleton) {
	G_message(_("Thin skeletons ..."));
	thin_skeleton(thresh);
	tie_up();
    }
    else {
	/* Close free ends by current region */
	ncoor = 0;
	acoor = 100;
	coor = (COOR *) G_malloc(sizeof(COOR) * acoor);

	nnodes = Vect_get_num_nodes(&Out);
	for (node = 1; node <= nnodes; node++) {
	    double x, y;

	    if (Vect_get_node_n_lines(&Out, node) < 2) {	/* add coordinates */
		Vect_get_node_coor(&Out, node, &x, &y, NULL);

		if (ncoor == acoor - 5) {	/* always space for 5 region corners */
		    acoor += 100;
		    coor = (COOR *) G_realloc(coor, sizeof(COOR) * acoor);
		}

		coor[ncoor].x = x;
		coor[ncoor].y = y;
		ncoor++;
	    }
	}

	/* Add region corners */
	coor[ncoor].x = Box.W;
	coor[ncoor].y = Box.S;
	ncoor++;
	coor[ncoor].x = Box.E;
	coor[ncoor].y = Box.S;
	ncoor++;
	coor[ncoor].x = Box.E;
	coor[ncoor].y = Box.N;
	ncoor++;
	coor[ncoor].x = Box.W;
	coor[ncoor].y = Box.N;
	ncoor++;

	/* Sort */
	qsort(coor, ncoor, sizeof(COOR), cmp);

	/* add last (first corner) */
	coor[ncoor].x = Box.W;
	coor[ncoor].y = Box.S;
	ncoor++;

	for (i = 1; i < ncoor; i++) {
	    if (coor[i].x == coor[i - 1].x && coor[i].y == coor[i - 1].y)
		continue;		/* duplicate */

	    Vect_reset_line(Points);
	    Vect_append_point(Points, coor[i].x, coor[i].y, 0.0);
	    Vect_append_point(Points, coor[i - 1].x, coor[i - 1].y, 0.0);
	    Vect_write_line(&Out, Type, Points, Cats);
	}

	G_free(coor);
    }

    if (in_area) {
	int ltype;

	/* Voronoi diagrams for areas */
	/* write input boundaries with an area on both sides to output */
	if (copybounds() > 0) {
	    int cat;
	    double x, y;
	    double snap_thresh;
	    int nmod;

	    /* snap Voronoi edges to boundaries */
	    snap_thresh = fabs(Box.W);
	    if (snap_thresh < fabs(Box.E))
		snap_thresh = fabs(Box.E);
	    if (snap_thresh < fabs(Box.N))
		snap_thresh = fabs(Box.N);
	    if (snap_thresh < fabs(Box.S))
		snap_thresh = fabs(Box.S);
	    snap_thresh = 2 * d_ulp(snap_thresh);

	    /* TODO: snap only Voronoi edges */
	    Vect_snap_lines(&Out, GV_BOUNDARY, snap_thresh, NULL);
	    do {
		Vect_break_lines(&Out, GV_BOUNDARY, NULL);
		Vect_remove_duplicates(&Out, GV_BOUNDARY, NULL);
		nmod =
		    Vect_clean_small_angles_at_nodes(&Out, GV_BOUNDARY, NULL);
	    } while (nmod > 0);

	    /* break lines */
	    Vect_break_lines(&Out, GV_BOUNDARY, NULL);

	    /* remove edge parts that are within an input area */
	    nlines = Vect_get_num_lines(&Out);
	    for (line = 1; line <= nlines; line++) {
		if (!Vect_line_alive(&Out, line))
		    continue;

		ltype = Vect_get_line_type(&Out, line);
		if (!(ltype & GV_BOUNDARY))
		    continue;

		Vect_read_line(&Out, Points, Cats, line);
		cat = 0;
		Vect_cat_get(Cats, 1, &cat);
		if (cat != 1)
		    continue;

		Vect_line_prune(Points);
		if (Points->n_points == 2) {
		    x = (Points->x[0] + Points->x[1]) / 2.0;
		    y = (Points->y[0] + Points->y[1]) / 2.0;
		}
		else {
		    i = 1;
		    while (Points->x[0] == Points->x[i] &&
		           Points->y[0] == Points->y[i] &&
			   i < Points->n_points - 1) {
			i++;
		    }
		    i = Points->n_points / 2.;
		    x = Points->x[i];
		    y = Points->y[i];
		}
		if (Vect_find_area(&In, x, y) > 0)
		    Vect_delete_line(&Out, line);
	    }
	}

	/* copy result to final output */
	Vect_reset_cats(Cats);

	nlines = Vect_get_num_lines(&Out);

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

	    if (!Vect_line_alive(&Out, line))
		continue;

	    ltype = Vect_get_line_type(&Out, line);
	    if (!(ltype & GV_BOUNDARY))
		continue;

	    Vect_read_line(&Out, Points, NULL, line);
	    Vect_line_prune(Points);

	    Vect_write_line(&Out2, GV_BOUNDARY, Points, Cats);
	}

	/* close temporary vector */
	Vect_close(&Out);
	/* is this a dirty hack ? */
	Out = Out2;
	Vect_build_partial(&Out, GV_BUILD_BASE);
    }

    nfields = Vect_cidx_get_num_fields(&In);
    cats = (int **)G_malloc(nfields * sizeof(int *));
    ncats = (int *)G_malloc(nfields * sizeof(int));
    fields = (int *)G_malloc(nfields * sizeof(int));
    for (i = 0; i < nfields; i++) {
	ncats[i] = 0;
	cats[i] =
	    (int *)G_malloc(Vect_cidx_get_num_cats_by_index(&In, i) *
			    sizeof(int));
	fields[i] = Vect_cidx_get_field_number(&In, i);
    }

    /* Copy input points */
    if (Type == GV_LINE)
	ctype = GV_POINT;
    else
	ctype = GV_CENTROID;

    nlines = Vect_get_num_lines(&In);

    G_important_message(_("Writing features..."));

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

	G_percent(line, nlines, 2);

	type = Vect_read_line(&In, Points, Cats, line);
	if (!(type & GV_POINTS))
	    continue;

	if (!skeleton) {
	    if (!Vect_point_in_box(Points->x[0], Points->y[0], 0.0, &Box))
		continue;

	    Vect_write_line(&Out, ctype, Points, Cats);
	}

	for (i = 0; i < Cats->n_cats; i++) {
	    int f, j;

	    f = -1;
	    for (j = 0; j < nfields; j++) {	/* find field */
		if (fields[j] == Cats->field[i]) {
		    f = j;
		    break;
		}
	    }
	    if (f > -1) {
		cats[f][ncats[f]] = Cats->cat[i];
		ncats[f]++;	    }
	}
    }

    /* Copy tables */
    if (!(flag.table->answer)) {
	int ttype, ntabs = 0;
	struct field_info *IFi, *OFi;

	/* Number of output tabs */
	for (i = 0; i < Vect_get_num_dblinks(&In); i++) {
	    int f, j;

	    IFi = Vect_get_dblink(&In, i);

	    f = -1;
	    for (j = 0; j < nfields; j++) {	/* find field */
		if (fields[j] == IFi->number) {
		    f = j;
		    break;
		}
	    }
	    if (f > -1) {
		if (ncats[f] > 0)
		    ntabs++;
	    }
	}

	if (ntabs > 1)
	    ttype = GV_MTABLE;
	else
	    ttype = GV_1TABLE;

	G_message(_("Writing attributes..."));
	for (i = 0; i < nfields; i++) {
	    int ret;

	    if (fields[i] == 0)
		continue;

	    G_debug(1, "Layer %d", fields[i]);

	    /* Make a list of categories */
	    IFi = Vect_get_field(&In, fields[i]);
	    if (!IFi) {		/* no table */
		G_message(_("No table"));
		continue;
	    }

	    OFi =
		Vect_default_field_info(&Out, IFi->number, IFi->name, ttype);

	    ret =
		db_copy_table_by_ints(IFi->driver, IFi->database, IFi->table,
				      OFi->driver,
				      Vect_subst_var(OFi->database, &Out),
				      OFi->table, IFi->key, cats[i],
				      ncats[i]);

	    if (ret == DB_FAILED) {
		G_warning(_("Cannot copy table"));
	    }
	    else {
		Vect_map_add_dblink(&Out, OFi->number, OFi->name, OFi->table,
				    IFi->key, OFi->database, OFi->driver);
	    }
	}
    }

    Vect_close(&In);
    
    if (Type == GV_BOUNDARY)
	clean_topo();

    /* build clean topology */
    Vect_build_partial(&Out, GV_BUILD_NONE);
    Vect_build(&Out);
    Vect_close(&Out);

    G_done_msg(" ");

    exit(EXIT_SUCCESS);
}
