
/****************************************************************************
 *
 * MODULE:       i.ortho.transform   (cloned from m.transform nee g.transform)
 * AUTHOR(S):    Brian J. Buckley
 *               Glynn Clements
 *               Hamish Bowman
 *               Markus Metz
 * PURPOSE:      Utility to compute transformation based upon GCPs and 
 *               output error measurements
 * COPYRIGHT:    (C) 2006-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.
 *
 *****************************************************************************/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <grass/gis.h>
#include <grass/glocale.h>
#include <grass/imagery.h>
#include <grass/glocale.h>
#include "orthophoto.h"

struct Max
{
    int idx;
    double val;
};

struct Stats
{
    struct Max x, y, g;
    double sum2, rms;
};

static int summary;
static int forward;
static char **columns;
static int need_fwd;
static int need_rev;
static int need_fd;
static int need_rd;
static char *coord_file;

struct Ortho_Image_Group group;

static struct Ortho_Control_Points *points;

static int count;
static struct Stats fwd, rev;

/* target fns taken from i.ortho.rectify */
static int which_env = -1;	/* 0 = cur, 1 = target */

int select_current_env(void)
{
    if (which_env < 0) {
	G_create_alt_env();
	which_env = 0;
    }
    if (which_env != 0) {
	G_switch_env();
	which_env = 0;
    }

    return 0;
}

int select_target_env(void)
{
    if (which_env < 0) {
	G_create_alt_env();
	which_env = 1;
    }
    if (which_env != 1) {
	G_switch_env();
	which_env = 1;
    }

    return 0;
}

static int get_target(void)
{
    char location[GMAPSET_MAX];
    char mapset[GMAPSET_MAX];
    char buf[1024];
    int stat;

    if (!I_get_target(group.name, location, mapset)) {
	sprintf(buf, _("Target information for group <%s> missing"), group.name);
	goto error;
    }

    sprintf(buf, "%s/%s", G_gisdbase(), location);
    if (access(buf, 0) != 0) {
	sprintf(buf, _("Target location <%s> not found"), location);
	goto error;
    }
    select_target_env();
    G_setenv_nogisrc("LOCATION_NAME", location);
    stat = G_mapset_permissions(mapset);
    if (stat > 0) {
	G_setenv_nogisrc("MAPSET", mapset);
	select_current_env();
	return 1;
    }
    sprintf(buf, _("Mapset <%s> in target location <%s> - "), mapset, location);
    strcat(buf, stat == 0 ? _("permission denied") : _("not found"));
  error:
    strcat(buf, "\n");
    strcat(buf, _("Please run i.target for group "));
    strcat(buf, group.name);
    G_fatal_error("%s", buf);
    return 1;			/* never reached */
}

static void update_max(struct Max *m, int n, double k)
{
    if (k > m->val) {
	m->idx = n;
	m->val = k;
    }
}

static void update_stats(struct Stats *st, int n, double dx, double dy,
			 double dg, double d2)
{
    update_max(&st->x, n, dx);
    update_max(&st->y, n, dy);
    update_max(&st->g, n, dg);
    st->sum2 += d2;
}

static void diagonal(double *dg, double *d2, double dx, double dy)
{
    *d2 = dx * dx + dy * dy;
    *dg = sqrt(*d2);
}

static void compute_transformation(void)
{
    int n, i, status;
    double e0, e1, e2, n0, n1, n2, z1, z2;
    struct Ortho_Control_Points temp_points;

    /* compute photo <-> image equations */
    group.ref_equation_stat = I_compute_ref_equations(&group.photo_points,
						      group.E12, group.N12,
						      group.E21, group.N21);

    if (group.ref_equation_stat <= 0)
	G_fatal_error(_("Error conducting transform (%d)"),
	              group.ref_equation_stat);

    /* compute target <-> photo equations */

    /* alloc and fill temp control points */
    temp_points.count = 0;
    temp_points.status = NULL;
    temp_points.e1 = NULL;
    temp_points.n1 = NULL;
    temp_points.z1 = NULL;
    temp_points.e2 = NULL;
    temp_points.n2 = NULL;
    temp_points.z2 = NULL;

    /* e0, n0, equal photo coordinates not image coords */
    for (i = 0; i < group.control_points.count; i++) {
	status = group.control_points.status[i];
	e1 = group.control_points.e1[i];
	n1 = group.control_points.n1[i];
	z1 = group.control_points.z1[i];
	e2 = group.control_points.e2[i];
	n2 = group.control_points.n2[i];
	z2 = group.control_points.z2[i];

	/* image to photo transformation */
	I_georef(e1, n1, &e0, &n0, group.E12, group.N12, 1);
	I_new_con_point(&temp_points, e0, n0, z1, e2, n2, z2, status);
    }


    group.con_equation_stat = I_compute_ortho_equations(&temp_points,
							&group.camera_ref,
							&group.camera_exp,
							&group.XC, &group.YC,
							&group.ZC,
							&group.omega,
							&group.phi,
							&group.kappa,
							&group.M,
							&group.MI);

    if (group.con_equation_stat <= 0)
	G_fatal_error(_("Error conducting transform (%d)"),
	              group.con_equation_stat);

    count = 0;

    for (n = 0; n < points->count; n++) {
	double etmp, ntmp;
	double fx, fy, fd, fd2;
	double rx, ry, rd, rd2;

	if (points->status[n] <= 0)
	    continue;

	count++;

	fd = fd2 = rd = rd2 = 0;

	if (need_fwd) {
	    /* image -> photo -> target */

	    /* image coordinates ex, nx to photo coordinates ex1, nx1 */
	    I_georef(points->e1[n], points->n1[n], &etmp, &ntmp, group.E12, group.N12, 1);

	    /* photo coordinates ex1, nx1 to target coordinates e1, n1 */
	    I_inverse_ortho_ref(etmp, ntmp, points->z1[n], &e2, &n2, &z2,
	                        &group.camera_ref,
				group.XC, group.YC, group.ZC, group.MI);

	    fx = fabs(e2 - points->e2[n]);
	    fy = fabs(n2 - points->n2[n]);

	    if (need_fd)
		diagonal(&fd, &fd2, fx, fy);

	    if (summary)
		update_stats(&fwd, n, fx, fy, fd, fd2);
	}

	if (need_rev) {
	    /* target -> photo -> image */

	    /* target coordinates e1, n1 to photo coordinates ex1, nx1 */
	    I_ortho_ref(points->e2[n], points->n2[n], points->z2[n],
	                &etmp, &ntmp, &z2, &group.camera_ref,
			group.XC, group.YC, group.ZC, group.M);

	    /* photo coordinates ex1, nx1 to image coordinates ex, nx */
	    I_georef(etmp, ntmp, &e1, &n1, group.E21, group.N21, 1);

	    rx = fabs(e1 - points->e1[n]);
	    ry = fabs(n1 - points->n1[n]);

	    if (need_rd)
		diagonal(&rd, &rd2, rx, ry);

	    if (summary)
		update_stats(&rev, n, rx, ry, rd, rd2);
	}

	if (!columns[0])
	    continue;

	if (coord_file)
	    continue;

	for (i = 0;; i++) {
	    const char *col = columns[i];

	    if (!col)
		break;

	    if (strcmp("idx", col) == 0)
		printf(" %d", n);
	    if (strcmp("src", col) == 0)
		printf(" %f %f", points->e1[n], points->n1[n]);
	    if (strcmp("dst", col) == 0)
		printf(" %f %f", points->e2[n], points->n2[n]);
	    if (strcmp("fwd", col) == 0)
		printf(" %f %f", e2, n2);
	    if (strcmp("rev", col) == 0)
		printf(" %f %f", e1, n1);
	    if (strcmp("fxy", col) == 0)
		printf(" %f %f", fx, fy);
	    if (strcmp("rxy", col) == 0)
		printf(" %f %f", rx, ry);
	    if (strcmp("fd", col) == 0)
		printf(" %f", fd);
	    if (strcmp("rd", col) == 0)
		printf(" %f", rd);
	}

	printf("\n");
    }

    if (summary && count > 0) {
	fwd.rms = sqrt(fwd.sum2 / count);
	rev.rms = sqrt(rev.sum2 / count);
    }
}

static void do_max(char name, const struct Max *m)
{
    printf("%c[%d] = %.2f\n", name, m->idx, m->val);
}

static void do_stats(const char *name, const struct Stats *st)
{
    printf("%s:\n", name);
    do_max('x', &st->x);
    do_max('y', &st->y);
    do_max('g', &st->g);
    printf("RMS = %.2f\n", st->rms);
}

static void analyze(void)
{
    if (group.ref_equation_stat == -1)
	G_warning(_("Poorly placed image to photo control points"));
    else if (group.con_equation_stat == -1)
	G_warning(_("Poorly placed image to target control points"));
    else if (group.ref_equation_stat == -2 || group.con_equation_stat == -2)
	G_fatal_error(_("Insufficient memory"));
    else if (group.ref_equation_stat < 0 || group.con_equation_stat < 0)
	G_fatal_error(_("Parameter error"));
    else if (group.ref_equation_stat == 0 || group.con_equation_stat == 0)
	G_fatal_error(_("No active control points"));
    else if (summary) {
	printf("Number of active points: %d\n", count);
	do_stats("Forward", &fwd);
	do_stats("Reverse", &rev);
    }
}

static void parse_format(void)
{
    int i;

    if (summary) {
	need_fwd = need_rev = need_fd = need_rd = 1;
	return;
    }

    if (!columns)
	return;

    for (i = 0;; i++) {
	const char *col = columns[i];

	if (!col)
	    break;

	if (strcmp("fwd", col) == 0)
	    need_fwd = 1;
	if (strcmp("fxy", col) == 0)
	    need_fwd = 1;
	if (strcmp("fd", col) == 0)
	    need_fwd = need_fd = 1;
	if (strcmp("rev", col) == 0)
	    need_rev = 1;
	if (strcmp("rxy", col) == 0)
	    need_rev = 1;
	if (strcmp("rd", col) == 0)
	    need_rev = need_rd = 1;
    }
}

static void dump_cooefs(void)
{
    int i;

    for (i = 0; i < 3; i++)
    	fprintf(stdout, "E%d=%.15g\n", i, forward ? group.E12[i] : group.E21[i]);

    for (i = 0; i < 3; i++)
    	fprintf(stdout, "N%d=%.15g\n", i, forward ? group.N12[i] : group.N21[i]);

    /* print ortho transformation matrix ? */
}

static void xform_value(double east, double north, double height)
{
    double e1, n1, z1, xe, xn, xz;

    if (forward) {
	/* image -> photo -> target */
	I_georef(east, north, &e1, &n1, group.E12, group.N12, 1);
	z1 = height;
	I_inverse_ortho_ref(e1, n1, z1, &xe, &xn, &xz, &group.camera_ref,
		    group.XC, group.YC, group.ZC, group.MI);
	xz = z1;
    }
    else {
	/* target -> photo -> image */
	I_ortho_ref(east, north, height, &e1, &n1, &z1, &group.camera_ref,
		    group.XC, group.YC, group.ZC, group.M);

	I_georef(e1, n1, &xe, &xn, group.E21, group.N21, 1);
	xz = 0.;
    }

    fprintf(stdout, "%.15g %.15g %.15g\n", xe, xn, xz);
}

static void do_pt_xforms(void)
{
    double easting, northing, height;
    int ret;
    FILE *fp;

    if (strcmp(coord_file, "-") == 0)
    	fp = stdin;
    else {
    	fp = fopen(coord_file, "r");
    	if (!fp)
    	    G_fatal_error(_("Unable to open file <%s>"), coord_file);
    }

    for (;;) {
    	char buf[1024];

    	if (!G_getl2(buf, sizeof(buf), fp))
    	    break;

    	if ((buf[0] == '#') || (buf[0] == '\0'))
    	    continue;

    	/* ? sscanf(buf, "%s %s", &east_str, &north_str)
    	    ? G_scan_easting(,,-1)
    	    ? G_scan_northing(,,-1) */
    	/* ? muliple delims with sscanf(buf, "%[ ,|\t]", &dummy) ? */

    	ret = sscanf(buf, "%lf %lf %lf", &easting, &northing, &height);
    	if (ret != 3)
    	    G_fatal_error(_("Invalid coordinates: [%s]"), buf);

    	xform_value(easting, northing, height);
    }

    if (fp != stdin)
    	fclose(fp);
}

int main(int argc, char **argv)
{
    struct Option *grp, *fmt, *xfm_pts;
    struct Flag *sum, *rev_flag, *dump_flag, *pan_flag;
    struct GModule *module;
    char *desc;
    char *camera;

    G_gisinit(argv[0]);

    /* Get Args */
    module = G_define_module();
    G_add_keyword(_("imagery"));
    G_add_keyword(_("orthorectify"));
    G_add_keyword(_("transformation"));
    G_add_keyword(_("GCP"));
    module->description =
	_("Computes a coordinate transformation based on the control points.");

    grp = G_define_standard_option(G_OPT_I_GROUP);

    fmt = G_define_option();
    fmt->key = "format";
    fmt->type = TYPE_STRING;
    fmt->required = NO;
    fmt->multiple = YES;
    fmt->options = "idx,src,dst,fwd,rev,fxy,rxy,fd,rd";
    desc = NULL;
    G_asprintf(&desc,
	        "idx;%s;src;%s;dst;%s;fwd;%s;rev;%s;fxy;%s;rxy;%s;fd;%s;rd;%s",
	        _("point index"),
	        _("source coordinates"),
	        _("destination coordinates"),
	        _("forward coordinates (destination)"),
	        _("reverse coordinates (source)"),
	        _("forward coordinates difference (destination)"),
	        _("reverse coordinates difference (source)"),
	        _("forward error (destination)"),
	        _("reverse error (source)"));
    fmt->descriptions = desc;
    fmt->answer = "fd,rd";
    fmt->description = _("Output format");

    sum = G_define_flag();
    sum->key = 's';
    sum->description = _("Display summary information");

    xfm_pts = G_define_standard_option(G_OPT_F_INPUT);
    xfm_pts->key = "coords";
    xfm_pts->required = NO;
    xfm_pts->label =
	_("File containing coordinates to transform (\"-\" to read from stdin)");
    xfm_pts->description = _("Local x,y,z coordinates to target east,north,height");

    rev_flag = G_define_flag();
    rev_flag->key = 'r';
    rev_flag->label = _("Reverse transform of coords file or coeff. dump");
    rev_flag->description = _("Target east,north,height coordinates to local x,y,z");

    dump_flag = G_define_flag();
    dump_flag->key = 'x';
    dump_flag->description = _("Display transform matrix coefficients");

    pan_flag = G_define_flag();
    pan_flag->key = 'p';
    pan_flag->description = _("Enable panorama camera correction");

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


    G_strip(grp->answer);
    strcpy(group.name, grp->answer);

    summary = !!sum->answer;
    columns = fmt->answers;
    forward = !rev_flag->answer;
    coord_file = xfm_pts->answer;

    if (pan_flag->answer)
	I_ortho_panorama();

    if (!I_get_ref_points(group.name, &group.photo_points)) {
	G_fatal_error(_("Can not read reference points for group <%s>"),
	              group.name);
    }
    if (!I_get_con_points(group.name, &group.control_points)) {
	group.control_points.count = 0;
    }
    
    camera = (char *)G_malloc(GNAME_MAX * sizeof(char));
    if (!I_get_group_camera(group.name, camera))
	G_fatal_error(_("No camera reference file selected for group <%s>"),
		      group.name);

    if (!I_get_cam_info(camera, &group.camera_ref))
	G_fatal_error(_("Bad format in camera file for group <%s>"),
		      group.name);

    /* get initial camera exposure station, if any */
    if (I_find_initial(group.name)) {
	if (!I_get_init_info(group.name, &group.camera_exp))
	    G_warning(_("Bad format in initial exposure station file for group <%s>"),
		      group.name);
    }

    /* get the target */
    get_target();
    
    points = &group.control_points;

    parse_format();

    /* I_compute_ortho_equations() must be run in the target location */
    select_target_env();
    compute_transformation();
    select_current_env();

    analyze();

    if (dump_flag->answer)
	dump_cooefs();

    if (coord_file)
	do_pt_xforms();

    return 0;
}
