
/****************************************************************************
 *
 * MODULE:       i.ortho.rectify (former i.photo.rectify)
 * AUTHOR(S):    Mike Baba,  DBA Systems, Inc. (original contributor)
 *               Markus Neteler <neteler itc.it>, 
 *               Bernhard Reiter <bernhard intevation.de>, 
 *               Glynn Clements <glynn gclements.plus.com>, 
 *               Hamish Bowman <hamish_b yahoo.com>,
 *               Markus Metz
 *
 * PURPOSE:      Rectifies an image by using the image to photo coordinate 
 *               and photo to target transformation matrices
 * COPYRIGHT:    (C) 1999-2010 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 "global.h"

int seg_mb_img, seg_mb_elev;

char *elev_name;
char *elev_mapset;

func interpolate;

struct Cell_head target_window;

void err_exit(struct Ref *, char *, char *);

/* modify this table to add new methods */
struct menu menu[] = {
    {p_nearest, "nearest", "nearest neighbor"},
    {p_bilinear, "linear", "linear interpolation"},
    {p_cubic, "cubic", "cubic convolution"},
    {p_lanczos, "lanczos", "lanczos filter"},
    {p_bilinear_f, "linear_f", "linear interpolation with fallback"},
    {p_cubic_f, "cubic_f", "cubic convolution with fallback"},
    {p_lanczos_f, "lanczos_f", "lanczos filter with fallback"},
    {NULL, NULL, NULL}
};

static char *make_ipol_list(void);

int main(int argc, char *argv[])
{
    char extension[INAME_LEN];
    char *ipolname;		/* name of interpolation method */
    int method;
    char *seg_mb;
    int i, m, k = 0;
    int got_file = 0, target_overwrite = 0;
    char *overstr;

    struct Ortho_Image_Group group;
    int *ref_list;
    int n;
    char *camera;
    char tl[100];
    char math_exp[100];
    char units[100];
    char nd[100];

    struct Cell_head cellhd, elevhd;

    struct Option *grp,         /* imagery group */
     *ifile,			/* input files */
     *ext,			/* extension */
     *tres,			/* target resolution */
     *mem,			/* amount of memory for cache */
     *angle,			/* camera angle relative to ground surface */
     *interpol;			/* interpolation method:
				   nearest neighbor, bilinear, cubic */

    struct Flag *c, *a, *pan_flag;
    struct GModule *module;


    G_gisinit(argv[0]);

    module = G_define_module();
    G_add_keyword(_("imagery"));
    G_add_keyword(_("orthorectify"));
    module->description =
	_("Orthorectifies an image by using the image to photo coordinate transformation matrix.");

    grp = G_define_standard_option(G_OPT_I_GROUP);

    ifile = G_define_standard_option(G_OPT_R_INPUTS);
    ifile->required = NO;

    ext = G_define_option();
    ext->key = "extension";
    ext->type = TYPE_STRING;
    ext->required = YES;
    ext->multiple = NO;
    ext->description = _("Output raster map(s) suffix");

    tres = G_define_option();
    tres->key = "resolution";
    tres->type = TYPE_DOUBLE;
    tres->required = NO;
    tres->description = _("Target resolution (ignored if -c flag used)");

    mem = G_define_standard_option(G_OPT_MEMORYMB);

    ipolname = make_ipol_list();

    interpol = G_define_option();
    interpol->key = "method";
    interpol->type = TYPE_STRING;
    interpol->required = NO;
    interpol->answer = "nearest";
    interpol->options = ipolname;
    interpol->description = _("Interpolation method to use");
    
    angle = G_define_standard_option(G_OPT_R_OUTPUT);
    angle->key = "angle";
    angle->required = NO;
    angle->description = _("Raster map with camera angle relative to ground surface");

    c = G_define_flag();
    c->key = 'c';
    c->description =
	_("Use current region settings in target location (def.=calculate smallest area)");

    a = G_define_flag();
    a->key = 'a';
    a->description = _("Rectify all raster maps in group");

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

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

    /* get the method */
    for (method = 0; (ipolname = menu[method].name); method++)
	if (strcmp(ipolname, interpol->answer) == 0)
	    break;

    if (!ipolname)
	G_fatal_error(_("<%s=%s> unknown %s"),
		      interpol->key, interpol->answer, interpol->key);
    interpolate = menu[method].method;

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

    seg_mb = NULL;
    if (mem->answer) {
	if (atoi(mem->answer) > 0)
	    seg_mb = mem->answer;
    }

    if (!ifile->answers)
	a->answer = 1;		/* force all */

    /* Find out how many files on command line */
    if (!a->answer) {
	for (k = 0; ifile->answers[k]; k++);
    }

    camera = (char *)G_malloc(GNAME_MAX * sizeof(char));
    elev_name = (char *)G_malloc(GNAME_MAX * sizeof(char));
    elev_mapset = (char *)G_malloc(GMAPSET_MAX * sizeof(char));

    /* find group */
    if (!I_find_group(group.name)) {
	G_fatal_error(_("Group <%s> not found"), group.name);
    }

    /* determine the number of files in this group */
    if (!I_get_group_ref(group.name, &group.group_ref)) {
	G_warning(_("Location: %s"), G_location());
	G_warning(_("Mapset: %s"), G_mapset());
	G_fatal_error(_("Could not read REF file for group <%s>"),
	              group.name);
    }

    if (group.group_ref.nfiles <= 0) {
	G_important_message(_("Group <%s> contains no raster maps; run i.group"),
			    grp->answer);
	exit(EXIT_SUCCESS);
    }

    ref_list = (int *)G_malloc(group.group_ref.nfiles * sizeof(int));

    if (a->answer) {
	for (n = 0; n < group.group_ref.nfiles; n++) {
	    ref_list[n] = 1;
	}
    }
    else {
	char xname[GNAME_MAX], xmapset[GMAPSET_MAX], *name, *mapset;

	for (n = 0; n < group.group_ref.nfiles; n++)
		ref_list[n] = 0;

	for (m = 0; m < k; m++) {
	    got_file = 0;
	    if (G_name_is_fully_qualified(ifile->answers[m], xname, xmapset)) {
		name = xname;
		mapset = xmapset;
	    }
	    else {
		name = ifile->answers[m];
		mapset = NULL;
	    }

	    got_file = 0;
	    for (n = 0; n < group.group_ref.nfiles; n++) {
		if (mapset) {
		    if (strcmp(name, group.group_ref.file[n].name) == 0 &&
		        strcmp(mapset, group.group_ref.file[n].mapset) == 0) {
			got_file = 1;
			ref_list[n] = 1;
			break;
		    }
		}
		else {
		    if (strcmp(name, group.group_ref.file[n].name) == 0) {
			got_file = 1;
			ref_list[n] = 1;
			break;
		    }
		}
	    }
	    if (got_file == 0)
		err_exit(&group.group_ref, ifile->answers[m], group.name);
	}
    }

    /** look for camera info for this block **/
    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);
    }

    /* panorama camera correction */
    if (pan_flag->answer)
	I_ortho_panorama();

    /* get the target */
    get_target(group.name);

    /* read the reference points for the group, compute image-to-photo trans. */
    get_ref_points(&group);

    /* read the control points for the group, convert to photo coords. */
    get_conz_points(&group);

    /* Check the GRASS_OVERWRITE environment variable */
    if ((overstr = getenv("GRASS_OVERWRITE")))  /* OK ? */
	target_overwrite = atoi(overstr);

    if (!target_overwrite) {
	/* check if output exists in target location/mapset */
	char result[GNAME_MAX];
	
	select_target_env();
	for (i = 0; i < group.group_ref.nfiles; i++) {
	    if (!ref_list[i])
		continue;

	    strcpy(result, group.group_ref.file[i].name);
	    strcat(result, extension);
	    
	    if (G_legal_filename(result) < 0)
		G_fatal_error(_("Extension <%s> is illegal"), extension);
		
	    if (G_find_raster2(result, G_mapset())) {
		G_warning(_("The following raster map already exists in"));
		G_warning(_("target LOCATION %s, MAPSET %s:"),
			  G_location(), G_mapset());
		G_warning("<%s>", result);
		G_fatal_error(_("Orthorectification cancelled."));
	    }
	}
	if (angle->answer) {
	    if (G_find_raster2(angle->answer, G_mapset())) {
		G_warning(_("The following raster map already exists in"));
		G_warning(_("target LOCATION %s, MAPSET %s:"),
			  G_location(), G_mapset());
		G_warning("<%s>", angle->answer);
		G_fatal_error(_("Orthorectification cancelled."));
	    }
	}
	
	select_current_env();
    }
    else
	G_debug(1, "Overwriting OK");

    /* do not use current region in target location */
    if (!c->answer) {
	double res = -1;
	
	if (tres->answer) {
	    if (!((res = atof(tres->answer)) > 0))
		G_warning(_("Target resolution must be > 0, ignored"));
	}
	/* get reference window from imagery group */
	get_ref_window(&group.group_ref, ref_list, &cellhd);
	georef_window(&group, &cellhd, &target_window, res);
    }

    G_verbose_message(_("Using region: N=%f S=%f, E=%f W=%f"), target_window.north,
	      target_window.south, target_window.east, target_window.west);

    G_debug(1, "Looking for elevation file in group: <%s>", group.name);

    /* get the block elevation layer raster map in target location */
    if (!I_get_group_elev(group.name, elev_name, elev_mapset, tl,
			 math_exp, units, nd))
	G_fatal_error(_("No target elevation model selected for group <%s>"),
		      group.name);

    G_debug(1, "Block elevation: <%s> in <%s>", elev_name, elev_mapset);

    /* get the elevation layer header in target location */
    select_target_env();
    Rast_get_cellhd(elev_name, elev_mapset, &elevhd);
    select_current_env();
    
    /* determine memory for elevation and imagery */
    seg_mb_img = seg_mb_elev = -1;
    if (seg_mb) {
	int max_rows, max_cols;
	int nx, ny;
	double max_mb_img, max_mb_elev;
	int seg_mb_total;

	max_rows = max_cols = 0;
	for (i = 0; i < group.group_ref.nfiles; i++) {
	    if (ref_list[i]) {
		Rast_get_cellhd(group.group_ref.file[i].name,
			     group.group_ref.file[i].mapset, &cellhd);
		if (max_rows < cellhd.rows)
		    max_rows = cellhd.rows;
		if (max_cols < cellhd.cols)
		    max_cols = cellhd.cols;
	    }
	}

	ny = (max_rows + BDIM - 1) / BDIM;
	nx = (max_cols + BDIM - 1) / BDIM;

	max_mb_img = ((double)nx * ny * sizeof(block)) / (1<<20);

	ny = (target_window.rows + BDIM - 1) / BDIM;
	nx = (target_window.cols + BDIM - 1) / BDIM;

	max_mb_elev = ((double)nx * ny * sizeof(block)) / (1<<20);

	if ((seg_mb_total = atoi(seg_mb)) > 0) {
	    seg_mb_elev = max_mb_elev * (seg_mb_total / (max_mb_img + max_mb_elev)) + 0.5;
	    seg_mb_img = max_mb_img * (seg_mb_total / (max_mb_img + max_mb_elev)) + 0.5;
	}
    }

    /* go do it */
    exec_rectify(&group, ref_list, extension, interpol->answer, angle->answer);

    G_done_msg(" ");

    exit(EXIT_SUCCESS);
}


void err_exit(struct Ref *ref, char *file, char *grp)
{
    int n;

    G_warning(_("Input raster map <%s> does not exist in group <%s>."),
	    file, grp);
    G_message(_("Try:"));

    for (n = 0; n < ref->nfiles; n++)
	G_message("%s@%s", ref->file[n].name, ref->file[n].mapset);

    G_fatal_error(_("Exit!"));
}

static char *make_ipol_list(void)
{
    int size = 0;
    int i;
    char *buf;

    for (i = 0; menu[i].name; i++)
	size += strlen(menu[i].name) + 1;

    buf = G_malloc(size);
    *buf = '\0';

    for (i = 0; menu[i].name; i++) {
	if (i)
	    strcat(buf, ",");
	strcat(buf, menu[i].name);
    }

    return buf;
}
