
/****************************************************************************
 *
 * MODULE:       r.los
 * AUTHOR(S):    Kewan Q. Khawaja, Intelligent Engineering Systems Laboratory, M.I.T. (original contributor)
 *               Markus Neteler <neteler itc.it> (original contributor)
 *               Ron Yorston <rmy tigress co uk>, Glynn Clements <glynn gclements.plus.com>,
 *               Hamish Bowman <hamish_b yahoo.com>, Jan-Oliver Wagner <jan intevation.de>
 * PURPOSE:      line of sight
 * COPYRIGHT:    (C) 1999-2007 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.
 *
 *****************************************************************************/

/****************************************************************
 *      main.c
 *
 *      This is the main program for line-of-sight analysis.
 *      It takes a digital elevation map and identifies all
 *      the grid cells that are visible from a user specified
 *      observer location.  Other input parameters to the
 *      program include the height of the observer above the
 *      ground, a map marking areas of interest in which the
 *      analysis is desired, and a maximum range for the line
 *      of sight.
 *
 ****************************************************************/

#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <fcntl.h>
#include <grass/gis.h>
#define MAIN
#include <grass/segment.h>
#include <grass/gprojects.h>
#include <grass/glocale.h>
#include "cmd_line.h"
#include "point.h"
#include "local_proto.h"

#define   COLOR_SHIFT      155.0
#define   COLOR_MAX        255.0

#define   SEARCH_PT_INCLINATION		SEARCH_PT->inclination
#define	  NEXT_SEARCH_PT		SEARCH_PT->next

struct Cell_head window;	/*      database window         */

int main(int argc, char *argv[])
{
    int row_viewpt, col_viewpt, nrows, ncols, a, b, row, patt_flag;
    int segment_no, flip, xmax, ymax, sign_on_y, sign_on_x;
    int submatrix_rows, submatrix_cols, lenth_data_item;
    int patt = 0, in_fd, out_fd, patt_fd = 0;
    int old, new;
    double slope_1, slope_2, max_vert_angle = 0.0, color_factor;
    char *old_mapset, *patt_mapset = NULL;
    FCELL *value;
    char *search_mapset, *current_mapset;
    char *in_name, *out_name, *patt_name = NULL;
    struct Categories cats;
    struct Cell_head cellhd_elev, cellhd_patt;
    extern struct Cell_head window;
    CELL *cell;
    FCELL *fcell, data, viewpt_elev;
    SEGMENT seg_in, seg_out, seg_patt;
    struct point *heads[16], *SEARCH_PT;
    struct GModule *module;
    struct Option *opt1, *opt2, *opt3, *opt5, *opt6, *opt7;
    struct Flag *curvature;
    struct History history;
    char title[128];
    double aa, e2;

    G_gisinit(argv[0]);

    module = G_define_module();
    module->keywords = _("raster, viewshed");
    module->description = _("Line-of-sight raster analysis program.");

    /* Define the different options */

    opt1 = G_define_standard_option(G_OPT_R_ELEV);
    opt1->key = "input";

    opt7 = G_define_standard_option(G_OPT_R_OUTPUT);

    opt3 = G_define_option();
    opt3->key = "coordinate";
    opt3->type = TYPE_STRING;
    opt3->required = YES;
    opt3->key_desc = "x,y";
    opt3->description = _("Coordinate identifying the viewing position");

    opt2 = G_define_standard_option(G_OPT_R_COVER);
    opt2->key = "patt_map";
    opt2->required = NO;
    opt2->description = _("Binary (1/0) raster map to use as a mask");

    opt5 = G_define_option();
    opt5->key = "obs_elev";
    opt5->type = TYPE_DOUBLE;
    opt5->required = NO;
    opt5->answer = "1.75";
    opt5->description = _("Viewing position height above the ground");

    opt6 = G_define_option();
    opt6->key = "max_dist";
    opt6->type = TYPE_DOUBLE;
    opt6->required = NO;
    opt6->answer = "10000";
    opt6->options = "0-5000000";	/* observer can be in a plane, too */
    opt6->description = _("Maximum distance from the viewing point (meters)");
    /* http://mintaka.sdsu.edu/GF/explain/atmos_refr/horizon.html */

    curvature = G_define_flag();
    curvature->key = 'c';
    curvature->description =
	_("Consider earth curvature (current ellipsoid)");

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

    /* initialize delayed point deletion */
    DELAYED_DELETE =  NULL;

    G_scan_easting(opt3->answers[0], &east, G_projection());
    G_scan_northing(opt3->answers[1], &north, G_projection());

    sscanf(opt5->answer, "%lf", &obs_elev);
    sscanf(opt6->answer, "%lf", &max_dist);
    elev_layer = opt1->answer;
    patt_layer = opt2->answer;
    out_layer = opt7->answer;

    G_get_window(&window);

    current_mapset = G_mapset();
    /* set flag to indicate presence of areas of interest   */
    if (patt_layer == NULL)
	patt_flag = FALSE;
    else
	patt_flag = TRUE;

    if ((G_projection() == PROJECTION_LL))
	G_fatal_error(_("Lat/Long support is not (yet) implemented for this module."));

    /* check if specified observer location inside window   */
    if (east < window.west || east > window.east
	|| north > window.north || north < window.south)
	G_fatal_error(_("Specified observer coordinate is outside current region bounds."));

    search_mapset = "";
    old_mapset = G_find_cell2(elev_layer, search_mapset);

    /*  check if elevation layer present in database    */
    if (old_mapset == NULL)
	G_fatal_error(_("Raster map <%s> not found"), elev_layer);

    /* if pattern layer used, check if present in database  */
    if (patt_flag == TRUE) {
	patt_mapset = G_find_cell(patt_layer, search_mapset);
	if (patt_mapset == NULL)
	    G_fatal_error(_("Raster map <%s> not found"), patt_layer);
    }

    /* check if specified output layer name is legal    */
    if (G_legal_filename(out_layer) < 0)
	G_fatal_error(_("<%s> is an illegal file name"), out_layer);

    /*  read header info for elevation layer        */
    if (G_get_cellhd(elev_layer, old_mapset, &cellhd_elev) < 0)
	G_fatal_error(_("[%s]: Cannot read map header"), elev_layer);

    /*  if pattern layer present, read in its header info   */
    if (patt_flag == TRUE) {
	if (G_get_cellhd(patt_layer, patt_mapset, &cellhd_patt) < 0)
	    G_fatal_error(_("[%s]: Cannot read map header"), patt_layer);

	/*  allocate buffer space for row-io to layer           */
	cell = G_allocate_raster_buf(CELL_TYPE);
    }

    /*  allocate buffer space for row-io to layer           */
    fcell = G_allocate_raster_buf(FCELL_TYPE);

    /*  find number of rows and columns in elevation map    */
    nrows = G_window_rows();
    ncols = G_window_cols();

    /*      open elevation overlay file for reading         */
    old = G_open_cell_old(elev_layer, old_mapset);
    if (old < 0)
	G_fatal_error(_("Unable to open raster map <%s>"), elev_layer);

    /*      open cell layer for writing output              */
    new = G_open_raster_new(out_layer, FCELL_TYPE);
    if (new < 0)
	G_fatal_error(_("Unable to create raster map <%s>"), out_layer);

    /* if pattern layer specified, open it for reading      */
    if (patt_flag == TRUE) {
	patt = G_open_cell_old(patt_layer, patt_mapset);
	if (patt < 0)
	    G_fatal_error(_("Unable to open raster map <%s>"), patt_layer);
	if (G_get_raster_map_type(patt) != CELL_TYPE)
	    G_fatal_error(_("Pattern map should be a binary 0/1 CELL map"));
    }

    /*      parameters for map submatrices                  */
    lenth_data_item = sizeof(FCELL);
    submatrix_rows = nrows / 4 + 1;
    submatrix_cols = ncols / 4 + 1;

    /* create segmented format files for elevation layer,   */
    /* output layer and pattern layer (if present)          */
    in_name = G_tempfile();
    in_fd = creat(in_name, 0666);
    segment_format(in_fd, nrows, ncols,
		   submatrix_rows, submatrix_cols, lenth_data_item);
    close(in_fd);
    out_name = G_tempfile();
    out_fd = creat(out_name, 0666);
    segment_format(out_fd, nrows, ncols,
		   submatrix_rows, submatrix_cols, lenth_data_item);
    close(out_fd);

    if (patt_flag == TRUE) {
	patt_name = G_tempfile();
	patt_fd = creat(patt_name, 0666);
	segment_format(patt_fd, nrows, ncols,
		       submatrix_rows, submatrix_cols, sizeof(CELL));
	close(patt_fd);
    }

    if (curvature->answer) {
	/* try to get the radius the standard GRASS way from the libs */
	G_get_ellipsoid_parameters(&aa, &e2);
	if (aa == 0) {
	    /* since there was a problem, take a hardcoded radius :( */
	    G_warning(_("Problem to obtain current ellipsoid parameters, using sphere (6370997.0)"));
	    aa = 6370997.00;
	}
	G_debug(3, "radius: %f", aa);
    }
    G_message(_("Using maximum distance from the viewing point (meters): %f"),
	      max_dist);

    /*      open, initialize and segment all files          */
    in_fd = open(in_name, 2);
    segment_init(&seg_in, in_fd, 4);
    out_fd = open(out_name, 2);
    segment_init(&seg_out, out_fd, 4);

    if (patt_flag == TRUE) {
	patt_fd = open(patt_name, 2);
	segment_init(&seg_patt, patt_fd, 4);
	for (row = 0; row < nrows; row++) {
	    if (G_get_raster_row(patt, cell, row, CELL_TYPE) < 0)
		G_fatal_error(_("Unable to read raster map <%s> row %d"),
			      patt_layer, row);
	    segment_put_row(&seg_patt, cell, row);
	}
    }

    for (row = 0; row < nrows; row++) {
	if (G_get_raster_row(old, fcell, row, FCELL_TYPE) < 0)
	    G_fatal_error(_("Unable to read raster map <%s> row %d"),
			  elev_layer, row);
	segment_put_row(&seg_in, fcell, row);
    }

    /* calc map array coordinates for viewing point         */
    row_viewpt = (window.north - north) / window.ns_res;
    col_viewpt = (east - window.west) / window.ew_res;

    /*       read elevation of viewing point                */
    value = &viewpt_elev;
    segment_get(&seg_in, value, row_viewpt, col_viewpt);
    viewpt_elev += obs_elev;

    /*      DO LOS ANALYSIS FOR SIXTEEN SEGMENTS            */
    for (segment_no = 1; segment_no <= 16; segment_no++) {
	sign_on_y = 1 - (segment_no - 1) / 8 * 2;

	if (segment_no > 4 && segment_no < 13)
	    sign_on_x = -1;
	else
	    sign_on_x = 1;

	/*      calc slopes for bounding rays of a segment      */
	if (segment_no == 1 || segment_no == 4 || segment_no == 5 ||
	    segment_no == 8 || segment_no == 9 || segment_no == 12 ||
	    segment_no == 13 || segment_no == 16) {
	    slope_1 = 0.0;
	    slope_2 = 0.5;
	}
	else {
	    slope_1 = 0.5;
	    slope_2 = 1.0;
	}

	if (segment_no == 1 || segment_no == 2 || segment_no == 7 ||
	    segment_no == 8 || segment_no == 9 || segment_no == 10 ||
	    segment_no == 15 || segment_no == 16)
	    flip = 0;
	else
	    flip = 1;

	/*      calculate max and min 'x' and 'y'               */
	a = ((ncols - 1) * (sign_on_x + 1) / 2 - sign_on_x * col_viewpt);
	b = (1 - sign_on_y) / 2 * (nrows - 1) + sign_on_y * row_viewpt;

	if (flip == 0) {
	    xmax = a;
	    ymax = b;
	}
	else {
	    xmax = b;
	    ymax = a;
	}

	/*      perform analysis for every segment              */
	heads[segment_no - 1] = segment(segment_no, xmax, ymax,
					slope_1, slope_2, flip, sign_on_y,
					sign_on_x, viewpt_elev, &seg_in,
					&seg_out, &seg_patt, row_viewpt,
					col_viewpt, patt_flag,
					curvature->answer, aa);

	G_percent(segment_no, 16, 5);
    }				/*      end of for-loop over segments           */

    /* loop over all segment lists to find maximum vertical */
    /* angle of any point when viewed from observer location */
    for (segment_no = 1; segment_no <= 16; segment_no++) {
	SEARCH_PT = heads[segment_no - 1];
	while (SEARCH_PT != NULL) {
	    if (fabs(SEARCH_PT_INCLINATION) > max_vert_angle)
		max_vert_angle = fabs(SEARCH_PT_INCLINATION);
	    SEARCH_PT = NEXT_SEARCH_PT;
	}
    }

    /* calculate factor to be multiplied to every vertical  */
    /* angle for suitable color variation on output map     */
    /*    color_factor = decide_color_range(max_vert_angle * 57.3,
       COLOR_SHIFT, COLOR_MAX);   */
    color_factor = 1.0;		/* to give true angle? */

    /* mark visible points for all segments on outputmap    */
    for (segment_no = 1; segment_no <= 16; segment_no++) {
	mark_visible_points(heads[segment_no - 1], &seg_out,
			    row_viewpt, col_viewpt, color_factor,
			    COLOR_SHIFT);
    }

    /*      mark viewpt on output map                       */
    data = 180;
    value = &data;
    segment_put(&seg_out, value, row_viewpt, col_viewpt);

    /* write pending updates by segment_put() to outputmap  */
    segment_flush(&seg_out);

    /* convert output submatrices to full cell overlay      */
    for (row = 0; row < nrows; row++) {
	int col;

	segment_get_row(&seg_out, fcell, row);
	for (col = 0; col < ncols; col++)
	    /* set to NULL if beyond max_dist (0) or blocked view (1) */
	    if (fcell[col] == 0 || fcell[col] == 1)
		G_set_null_value(&fcell[col], 1, FCELL_TYPE);
	if (G_put_raster_row(new, fcell, FCELL_TYPE) < 0)
	    G_fatal_error(_("Failed writing raster map <%s> row %d"),
			  out_layer, row);
    }

    segment_release(&seg_in);	/* release memory       */
    segment_release(&seg_out);

    if (patt_flag == TRUE)
	segment_release(&seg_patt);

    close(in_fd);		/* close all files      */
    close(out_fd);
    unlink(in_name);		/* remove temp files as well */
    unlink(out_name);
    G_close_cell(old);
    G_close_cell(new);

    if (patt_flag == TRUE) {
	close(patt_fd);
	G_close_cell(patt);
    }

    /*      create category file for output map             */
    G_read_cats(out_layer, current_mapset, &cats);
    G_set_cats_fmt("$1 degree$?s", 1.0, 0.0, 0.0, 0.0, &cats);
    G_write_cats(out_layer, &cats);

    sprintf(title, "Line of sight %.2fm above %s", obs_elev, opt3->answer);
    G_put_cell_title(out_layer, title);
    G_write_raster_units(out_layer, "degrees");

    G_short_history(out_layer, "raster", &history);
    G_command_history(&history);
    G_write_history(out_layer, &history);

    /* release that last tiny bit of memory ... */
    if ( DELAYED_DELETE != NULL ) {
        G_free ( DELAYED_DELETE );
    }

    exit(EXIT_SUCCESS);
}
