/*
 *****************************************************
 *
 *  SaVi by Robert Thurman (thurman@geom.umn.edu) and
 *          Patrick Worfolk (worfolk@alum.mit.edu).
 *
 *  Copyright (c) 1997 by The Geometry Center.
 *  This file is part of SaVi.  SaVi is free software;
 *  you can redistribute it and/or modify it only under
 *  the terms given in the file COPYRIGHT which you should
 *  have received along with this file.  SaVi may be
 *  obtained from:
 *  http://savi.sourceforge.net/
 *  http://www.geom.uiuc.edu/locate/SaVi
 *
 *****************************************************
 *
 * coverage_vis.c
 *
 * Coverage visualization routines.
 *
 * $Id: coverage_vis.c,v 1.58 2005/02/27 00:23:09 lloydwood Exp $
 */

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

#ifndef NO_ZLIB
#include <zlib.h>
#endif /* NO_ZLIB */

#include "constants.h"
#include "globals.h"
#include "gv_file.h"
#include "gv_utils.h"
#include "orbit_utils.h"
#include "savi.h"
#include "stats_utils.h"

#include "coverage_vis.h"

extern Tcl_Interp *interp;	/* Interpreter for this application. */

extern int tcl_script(char[]);

extern int no_access_flag;
extern int map_flag;

extern unsigned int geomview_flag;
extern unsigned int earth_on_flag;
extern unsigned int earth_geom_exists;
extern unsigned int use_fancy_earth;
extern unsigned int given_temp_scratchfile;

static int current_proj = -1; /* whatever the default, we start by drawing */

static char imagename[] = "im_coverage";

static const char format1[] =
  "set coverage \"Current global coverage: %.1f%%\"";
static const char format2[] =
 "set mean_coverage \"Averaged global coverage: %.1f%%\"";

static void write_image_as_ppm(FILE *f);

#define LENGTH_APPEARANCE_FILE 1024
#define LENGTH_TEMPFILE_NAME 256
#define LENGTH_LINE 128
static char dynamic_description[LENGTH_APPEARANCE_FILE];
static char static_description[LENGTH_APPEARANCE_FILE];
static char temp_scratchfile[LENGTH_TEMPFILE_NAME];

static void append_file_suffix(char * filename);
static char * append_string(char * string, const char * add);

#ifndef NO_ZLIB
/* used only in this file */
static void write_image_as_ppm_gz(gzFile *f);
static void write_image_to_file_gz(char *name);
#endif /* NO_ZLIB */


static void set_coverage(int projection, grid *g);

static void grid_and_foreground_to_image(int projection, grid * g,
					 int fgOnly);
static int new_foreground(int projection, grid * g, unsigned int force);
static void draw_sin_map_edges(grid * g);
static Tk_PhotoImageBlock *get_image(void);
static void overlay_bitmap(unsigned int h, unsigned int w, unsigned char *b,
			   const char *filename);
static void latlon_to_grid_index(int grid_index[2], LatLon * pl, int projection,
				 grid * g);

static Tk_PhotoImageBlock *image = NULL;
static unsigned char *foreground = NULL;

#define PIXELSIZE 3		/* 3 for RGB */

typedef struct RGB {
    unsigned char red, green, blue;
} RGB;

/* Colors for coverage intensity (in RGB values)  */
/* Any number of colors may be inserted.          */
static unsigned char colors[] = { 255, 255, 255, /* no coverage */
				  255, 255, 0,  /* single coverage */
				  255, 170, 0, /* double coverage */
				  255, 85, 0, /* triple coverage */
				  255, 0, 0  /* four or more */
};
#define num_colors sizeof(colors)/sizeof(unsigned char)/PIXELSIZE

/* Color for earth outline and ground track */
static unsigned char outline_colors[] = { 160, 192, 240,  /* background */
					  0, 0, 0,	 /* earth outline */
					  0, 255, 0,    /* projected ground track */
					  255, 0, 255, /* selected ground track */
					  0, 255, 0,     /* satellite position */
					  0, 255, 0,    /* rest of cross */
					  255, 0, 255,   /* selected satellite */
					  255, 0, 255   /* rest of cross */
};
#define num_outline_colors sizeof(outline_colors)/sizeof(unsigned char)/PIXELSIZE

/* Color for noaccess */
static unsigned char noaccess_colors[] = { 255, 255, 255, /* no intervals missed */
					   0, 0, 255,	 /* one interval missed */
					   0, 0, 217,	/* two intervals missed */
					   0, 0, 180,  /* three intervals missed */
					   0, 0, 143  /* >three intervals missed */
};
#define num_noaccess_colors sizeof(noaccess_colors)/sizeof(unsigned char)/PIXELSIZE

static unsigned char * COLDIV = &colors[0];
static unsigned char * COLDEC = &noaccess_colors[0];

/*
 * color_hex - turn combined RRGGBB byte into three RGB bytes.
 */
static void
color_hex(int combined, RGB * color_set) {
  unsigned int rrggbb = (unsigned int) combined;

  color_set->blue = rrggbb & 0x0000FF;
  rrggbb = rrggbb >> 8;
  color_set->green = rrggbb & 0x0000FF;
  rrggbb = rrggbb >> 8;
  color_set->red = rrggbb & 0x0000FF;
}

/*
 * coverage_color_copy - slave color arrays to Tcl values
 */
void
coverage_color_copy(void)
{
  color_hex(DIV1, (RGB *) &COLDIV[3]);
  color_hex(DIV2, (RGB *) &COLDIV[6]);
  color_hex(DIV3, (RGB *) &COLDIV[9]);
  color_hex(DIV4, (RGB *) &COLDIV[12]);

  color_hex(DEC1, (RGB *) &COLDEC[3]);
  color_hex(DEC2, (RGB *) &COLDEC[6]);
  color_hex(DEC3, (RGB *) &COLDEC[9]);
  color_hex(DEC4, (RGB *) &COLDEC[12]);
}

int
coverage_uses_zlib(void) {

#ifndef NO_ZLIB
  return TRUE;
#else
  return FALSE;
#endif /* NO_ZLIB */

}

/*
 * image_init - Set up the Tk image structure based on the coverage grid.
 */
void
image_init(grid * g)
{
  unsigned int height, width, image_size;

  height = g->height;
  width = g->width;
  image_size = height * width;

  image = (Tk_PhotoImageBlock *) malloc(sizeof(Tk_PhotoImageBlock));
  image->pixelPtr = (unsigned char *) malloc(image_size * PIXELSIZE *
					     sizeof(unsigned char *));
  image->width = width;
  image->height = height;
  image->pitch = width * PIXELSIZE;
  image->pixelSize = PIXELSIZE;
  image->offset[0] = 0;
  image->offset[1] = 1;
  image->offset[2] = 2;

  if (!foreground) {
    foreground = (unsigned char *) malloc(image_size * sizeof(unsigned char *));

    cyl_foreground = (unsigned char *) calloc(sizeof(unsigned char *), image_size);
    sin_foreground = (unsigned char *) calloc(sizeof(unsigned char *), image_size);
    unp_foreground = (unsigned char *) calloc(sizeof(unsigned char *), image_size);
    sph_foreground = (unsigned char *) calloc(sizeof(unsigned char *), image_size);

    if (width == IMAGE_LARGE_WIDTH) {
      overlay_bitmap(height, width, cyl_foreground, CYLINDRICAL_LARGE_BITMAP_NAME);
      overlay_bitmap(height, width, sin_foreground, SINUSOIDAL_LARGE_BITMAP_NAME);
      overlay_bitmap(height, width, unp_foreground, UNPROJECTED_LARGE_BITMAP_NAME);
      overlay_bitmap(height, width, sph_foreground, SPHERICAL_LARGE_BITMAP_NAME);
    } else if (width == IMAGE_WIDTH) {
      overlay_bitmap(height, width, cyl_foreground, CYLINDRICAL_BITMAP_NAME);
      overlay_bitmap(height, width, sin_foreground, SINUSOIDAL_BITMAP_NAME);
      overlay_bitmap(height, width, unp_foreground, UNPROJECTED_BITMAP_NAME);
      overlay_bitmap(height, width, sph_foreground, SPHERICAL_BITMAP_NAME);
    } else {
      error("no appropriately-sized background map for coverage window.");
    }
    draw_sin_map_edges(g);
  }
}

static void
write_image_as_ppm(FILE *f)
{
  unsigned int w, h, length;
  unsigned char *p;

  /* this was grid g->width, but for temporary convenience... */
  w = Image_Width;
  h = Image_Height;

  /*
   * Write header file describing grid
   */
  fprintf(f,"P6\n%i %i\n255\n", w, h);

  p = image->pixelPtr;
  length = w * h * PIXELSIZE;
  fwrite(p, sizeof(unsigned char), length, f);
}

void
write_image_to_file(char * name)
{
  FILE *f;

  if (NULL == (f = fopen(name, "w"))) {
    fprintf(stderr,"unable to save coverage map to %s", name);
    return;
  }

  write_image_as_ppm(f);

  fclose(f);
}

#ifndef NO_ZLIB
/* function requires zlib */
static void
write_image_as_ppm_gz(gzFile *f)
{
  unsigned int w, h, length;
  unsigned char *p;

  /* this was grid g->width, but for temporary convenience... */
  w = Image_Width;
  h = Image_Height;

  /*
   * Write header file describing grid
   */
  gzprintf(f,"P6\n%i %i\n255\n", w, h);

  length = w * h * PIXELSIZE * sizeof(unsigned char);
  p = image->pixelPtr;
  gzwrite(f, p, length);
}

/* function requires zlib */
static void
write_image_to_file_gz(char * name)
{
  gzFile *f;

  if (NULL == (f = gzopen(name, "w"))) {
    fprintf(stderr,"unable to save compressed coverage map to %s", name);
    return;
  }

  gzsetparams(name, Z_BEST_COMPRESSION, Z_RLE); /* Z_RLE for PNG-style data */
  write_image_as_ppm_gz(f);

  gzclose(f);
}
#endif /* NO_ZLIB */

static void
append_file_suffix(char * filename) {
  char * string;

  string = filename + strlen(filename);

  if (coverage_uses_zlib()) {
    strcpy(string,"-savi.ppm.gz");
  } else {
    strcpy(string,"-savi.ppm");
  }
}

static char *
append_string(char * string, const char * add)
{
  strcpy(string, add);
  return(string + strlen(add));
}

int
coverage_tempfile_created(void)
{
  char line[LENGTH_LINE];
  const char name[] = "oogl/earth.oogl";
  const char fallback_tmpnam[] = "coverage";
  const char static_name[] = "oogl/Earth.ppm.Z";
  const char file_separator[] = "/";
  const char file_start[] = "file \"";
  const char file_end[] = "\"\n";
  char *dynamic_description_end, *static_description_end, *string, *path;
  const char null_char = 0;
  const char tab_char = 9;
  const char space_char = 32;
  const char comment_char = 35; /* # */
  unsigned int replaced = 0;
  unsigned int add_static_length = 0;
  unsigned int add_dynamic_length = 0;
  unsigned int incr_length;
  FILE *f, *g;

  if (NULL == (f = fopen(name, "r"))) {
    fprintf(stderr,"unable to open texturemap description %s", name);
    return(FALSE);
  }

  string = tmpnam(NULL);
  strcpy(temp_scratchfile, string);
  append_file_suffix(temp_scratchfile);

  if (NULL == (g = fopen(temp_scratchfile, "wb+"))) {
    fprintf(stderr,"SaVi couldn't open assigned tempfile %s\n", temp_scratchfile);
    path = getenv("HOME");
    if (!path) {
      error("couldn't learn home directory $HOME for fallback scratchfile");
      return(FALSE);
    }
    if (strlen(path) > LENGTH_TEMPFILE_NAME - 16) {
      error("pathname of home directory $HOME too long to store.");
      return(FALSE);
    }
    string = append_string(temp_scratchfile, path);
    string = append_string(string, file_separator);
    string = append_string(string, fallback_tmpnam);
    append_file_suffix(temp_scratchfile);
    if (NULL == (g = fopen(temp_scratchfile, "wb+"))) {
      fprintf(stderr,"SaVi couldn't open fallback tempfile %s", temp_scratchfile);
      return(FALSE);
    }
  }

  fclose(g);

  fprintf(stderr,"SaVi: temporary texturemap scratchfile is %s\n",
	  temp_scratchfile);
  if (!coverage_uses_zlib()) {
    error("texturemaps will be uncompressed. Compile SaVi with zlib for compressed.");
  }
  if (!coverage_large_map) {
    error("dynamic texture mapping will benefit from large coverage map (-large-map).");
  }

  dynamic_description_end = dynamic_description;
  static_description_end = static_description;

  while (fgets(line,100,f) != NULL) {
    string = line;
    while ((*string == space_char) || (*string == tab_char)) {
      /* skip leading whitespace */
      string++;
    }
    if ((*string == comment_char) || (string == null_char)) {
      /* skip comments and empty lines */
      continue;
    }
    if (strstr(string,"Earth.ppm.Z")) {
      /* replace with our file line */
      incr_length = strlen(file_start) + strlen(temp_scratchfile) +
	            strlen(file_end);
      add_dynamic_length += incr_length;
      path=getenv("SAVI");
      incr_length = strlen(file_start) + strlen(path) + strlen(file_separator) +
	            strlen(static_name) + strlen(file_end);
      add_static_length += incr_length;
      if ((add_dynamic_length > LENGTH_APPEARANCE_FILE) ||
	  (add_static_length > LENGTH_APPEARANCE_FILE)) {
	fprintf(stderr, "Tempfile pathname %s is too long to cache.",
		temp_scratchfile);
	return(FALSE);
      }
      dynamic_description_end = append_string(dynamic_description_end, file_start);
      dynamic_description_end = append_string(dynamic_description_end, temp_scratchfile);
      dynamic_description_end = append_string(dynamic_description_end, file_end);
      static_description_end = append_string(static_description_end, file_start);
      static_description_end = append_string(static_description_end, path);
      static_description_end = append_string(static_description_end, file_separator);
      static_description_end = append_string(static_description_end, static_name);
      static_description_end = append_string(static_description_end, file_end);
      replaced++;
      continue;
    }

    incr_length = strlen(string);
    add_dynamic_length += incr_length;
    add_static_length += incr_length;

    if ((add_static_length > LENGTH_APPEARANCE_FILE) ||
	(add_dynamic_length > LENGTH_APPEARANCE_FILE)) {
      fprintf(stderr, "Earth appearance in %s is too long to cache.",
	      name);
      return(FALSE);
    }

    dynamic_description_end = append_string(dynamic_description_end, string);
    static_description_end = append_string(static_description_end, string);
  }

  if (!replaced) {
    fprintf(stderr, "SaVi: did not find file \"Earth.ppm.Z\" in %s to replace with temp file location.", name);
  } else if (replaced > 1) {
    fprintf(stderr, "SaVi: found multiple instances of file \"Earth.ppm.Z\" in %s. Problems?", name);
  }

  return(TRUE);
}

void
coverage_tempfile_cleanup(void) 
{
  if (given_temp_scratchfile) {
    remove(temp_scratchfile);
    fprintf(stderr, "SaVi: cleaned up temporary scratchfile %s\n",
	    temp_scratchfile);
  }
}

void
reset_foreground(int projection, grid * g)
{
  if (foreground)
    new_foreground(projection, g, TRUE);
}


/*
 * get_image - Return pointer to the image structure.
 */
static Tk_PhotoImageBlock *
get_image(void)
{
  if (!image) {
    error("get_image: image not yet initialized.");
  }

  return image;
}

/*
 * update_display - Update the Tk coverage image, given a completed coverage grid.
 */
void
update_display(int projection, grid * g, int coverage_flag, int tracks_flag,
	       int project_tracks_flag)
{
  Tk_PhotoHandle handle;

  /*
   * Re-write the entire image, unless we are just displaying ground
   * tracks, and there's not a new projection, in which case we can
   * simply write the foreground (which includes ground tracks).
   */
  int fgOnly = !new_foreground(projection, g, FALSE) && !coverage_flag &&
	       !tracks_flag && !project_tracks_flag;
  grid_and_foreground_to_image(projection, g, fgOnly);

  handle = Tk_FindPhoto(interp, imagename);
  if (handle) {
    /* double width of image to tile horizontally */
#if ( (TK_MAJOR_VERSION > 7) && (TK_MINOR_VERSION > 3) )
   /* changed for version 8.4, September 2002. */
    Tk_PhotoPutBlock(handle, get_image(), 0, 0, image->width*2, image->height,
        TK_PHOTO_COMPOSITE_OVERLAY);
#else
    Tk_PhotoPutBlock(handle, get_image(), 0, 0, image->width*2, image->height);
#endif
  } else {
    error("coverage_display: bad image name.");
  }

  /* display coverage percentages */
  set_coverage(projection, g);
}

/*
 * Clear the intensity array.
 */
void
clear_intensity(grid * g)
{
  unsigned int block;

  block = sizeof(int) * g->height * g->width;
  memset(g->data, 0, (size_t) block);
  memset(g->covered, 0, (size_t) sizeof(int) * g->height);
  g->count = 0;
}

/*
 * fill interval-decay with high value meaning many intervals since coverage
 */
void
clear_interval(grid * g)
{
  unsigned int block;

  block = sizeof(int) * g->height * g->width;
  memset(g->noaccess, num_noaccess_colors, (size_t) block);
}

/*
 * Increment everything on the interval grid towards decay.
 */
void
decay_interval(grid * g)
{
  unsigned int i;
  unsigned int image_size = g->height * g->width;

  for (i = 0; i < image_size; i++) {
  	(g->noaccess[i])++;
  }
}

/*
 * set_coverage - Display total percent coverage.
 */
static void
set_coverage(int projection, grid * g)
{
  char cmd[sizeof(format1) + 5];

  sprintf(cmd, format1, total_coverage(projection, TRUE, g));
  tcl_script(cmd);
  sprintf(cmd, format2, mean_coverage(projection, g));
  tcl_script(cmd);
}

/*
 * overlay_bitmap - Takes a bitmap and lies it on top of our image array.
 * h,w = height, width of the image array.
 */
static void
overlay_bitmap(unsigned int h, unsigned int w, unsigned char *b,
	       const char filename[])
{
  FILE *f;
  unsigned int pbmtype, image_size, width, height, i, j, c, mask;

  if (NULL == (f = fopen(filename, "r")))
    return;

  forward_over_comments(f);
  fscanf(f, "P%d", &pbmtype);
  forward_over_comments(f);
  fscanf(f, "%d", &width);
  forward_over_comments(f);
  fscanf(f, "%d", &height);
  /* skip one char */
  c = getc(f);

  if ((pbmtype == 4) && (width == w) && (height == h)) {
    image_size = h*w;
    for (i = 0; i < image_size; i += 8) {
      c = getc(f);
      mask = 0x80;
      for (j = 0; j < 8; j++) {
	if (c & mask) {
	  *(b++) = 1;
	} else {
	  b++;
	}
	mask = mask / 2;
      }
    }
  } else {
    fprintf(stderr, "Error in bitmap format: P%d %d %d\n",
		pbmtype, width, height);
  }

  fclose(f);
}

static void
draw_sin_map_edges(grid * g)
{
  SphericalCoordinates point;
  int left[2], right[2];
  unsigned int h = g->height;
  unsigned int w = g->width;
  unsigned int i;

  point.r = 1;
  point.theta = 0;
  point.phi = 0;

  for (i = 0; i < h; i++) {
    intensity_edges(left, right, &point, SINUSOIDAL, g);
    sin_foreground[(w * i + left[0])] = 1;
    sin_foreground[(w * i + right[0])] = 1;
    point.phi += PI / (h - 1);	/* Increment phi by one pixel */
  }
}

/*
 * new_foreground - Initialize foreground intensity image, including earth outline.
 * Returns TRUE if foreground must be re-drawn, FALSE otherwise.
 */
static int
new_foreground(int projection, grid * g, unsigned int force)
{
  unsigned int image_size;

  if (!force && (projection == current_proj))
    return (FALSE);

  if (current_proj != projection) {
    current_proj = projection;
    clear_intensity(g);
    clear_interval(g);
  }

  image_size = g->height * g->width;

  switch (projection) {
  case UNPROJECTED:
    Longitude_Center_Line = LONGITUDE_CENTER_LINE;
    memcpy(foreground, unp_foreground, image_size);
    break;
  case SINUSOIDAL:
    Longitude_Center_Line = LONGITUDE_CENTER_LINE_SINU;
    memcpy(foreground, sin_foreground, image_size);
    break;
  case SPHERICAL:
    Longitude_Center_Line = LONGITUDE_CENTER_LINE;
    memcpy(foreground, sph_foreground, image_size);
    break;
  case CYLINDRICAL:
  default:
    Longitude_Center_Line = LONGITUDE_CENTER_LINE;
    memcpy(foreground, cyl_foreground, image_size);
  }

  return (TRUE);
}

/*
 * Copy foreground (land masses and ground tracks, if any) only onto image.
 * The routine re-draws an image pixel only for corresponding foreground array
 * elements which are non-zero.
 */
static void
grid_and_foreground_to_image(int projection, grid * g, int fgOnly)
{
  unsigned int i, j, offset, ci;
  unsigned int w = g->width;
  unsigned int h = g->height;
  unsigned int num_colors_dec, num_noaccess_colors_dec;
  unsigned int data_value, map_threshold, send_coverage;
  int drawWidth = w;
  int middleWidth = w/2;
  unsigned int sinu_surround = FALSE;
  unsigned int sph_surround = FALSE;
  unsigned char *c;
  unsigned char *f = foreground;
  unsigned char fg, cross_fg;
  unsigned char *p = image->pixelPtr;
  unsigned char *q, *r;
  float lat, width, fw;

  /*
   * Do we need to draw a blank surround outside
   * the map?
   */
  if (!fgOnly) {
    if (projection == SINUSOIDAL) {
      sinu_surround = TRUE;
    }
    if (projection == SPHERICAL) {
      sph_surround = TRUE;
    }
  }

  fw = (float) w;
  num_colors_dec = num_colors - 1;
  num_noaccess_colors_dec = num_noaccess_colors - 1;

  send_coverage = geomview_flag && geomview_dynamic_texture_flag &&
                  texture_flag && (projection == CYLINDRICAL) &&
                  earth_on_flag && use_fancy_earth && earth_geom_exists;

   /*
    * If threshold is 0, we don't draw white background (0), but we
    * do draw map outline (1) above that. If 1, we will not draw
    * map outline. For 0/1, could have used || instead of MAX,
    * but MAX is clearer in intent.
    */
  map_threshold = MAX((send_coverage && !geomview_texture_with_map), !map_flag);

  for (i = 0; i < h; i++) {
    lat = 90.0 - 180.0 * i / h;
    if (sinu_surround) {
      width = (fw * cos(DEG_TO_RAD * lat));
      drawWidth = (int) (0.5 + (fw - width) / 2);
    }

    if (sph_surround) {
      width = fw/4 - sqrt(fw*fw/16-fw*fw*lat*lat/16/90/90);
      drawWidth = (int) width;
      middleWidth = w/2 - drawWidth;
    }

    offset = i * w;
    q = p;

    for (j = 0; j < w; j++, p += PIXELSIZE) {
      fg = *(f++);

      if (sinu_surround || sph_surround) {
	q = p; /* updating for cross wrap-around */
	if ((j <= drawWidth) || j >= (w - drawWidth)) {
	  p[0] = outline_colors[0];
	  p[1] = outline_colors[1];
	  p[2] = outline_colors[2];
	  continue;
	}
	if (sph_surround && (j > middleWidth) && (j < w - middleWidth)) {
	  p[0] = outline_colors[0];
	  p[1] = outline_colors[1];
	  p[2] = outline_colors[2];
	  continue;
	}
      }

      if (fg > map_threshold) {
	/* skipping Earth outline if map is to be sent */
	c = outline_colors + PIXELSIZE * fg;

	p[0] = c[0];
	p[1] = c[1];
	p[2] = c[2];

        /*
	 * Indicate sub-satellite point by drawing four arms of cross.
	 */
	if ((fg == GROUND_TRACKS_CI) || (fg == SPECIAL_GROUND_TRACKS_CI)) {
	  cross_fg = fg + 1;

	  if (j > 0) {
	    /* draw left arm of cross */
	    *(f-2) = cross_fg; /* set retroactively for future updates. */
	    r = p - PIXELSIZE;
	    r[0] = c[0];
	    r[1] = c[1];
	    r[2] = c[2];
	  } else {
	    /* left arm wrap around - set pixel at j=w */
	    /* not well-matched to sinusoidal */
	    *(f+w-2) = cross_fg; /* ready to draw at end of this line */
	  }
	  if (i > 0) {
	    /* draw top arm of cross */
	    *(f-w-1) = cross_fg; /* set retroactively for future updates. */
	    r = p - w * PIXELSIZE;
	    r[0] = c[0];
	    r[1] = c[1];
	    r[2] = c[2];
	  }
	  if (j < w-1) {
	    /* ready to draw right arm of cross, next j iteration */
	    *f = cross_fg;
	  } else {
	    /* right arm wrap around - set pixel at j=0 i same value */
	    /* not well-matched to sinusoidal */
	    *(f-j) = cross_fg;
	    q[0] = c[0];
	    q[1] = c[1];
	    q[2] = c[2];
	  }
	  if (i < h-1) {
	    /* ready to draw bottom of cross, next i iteration/row pass */
	    *(f-1 + w) = cross_fg;
	  }
	}

	continue;
      }

      if (!fgOnly) {
	data_value = g->data[offset + j];
	if (data_value || !no_access_flag) {
	  ci = MIN(data_value, num_colors_dec);
	  c = colors + PIXELSIZE * ci;
	} else {
	  ci = MIN(g->noaccess[offset + j], num_noaccess_colors_dec);
	  c = noaccess_colors + PIXELSIZE * ci;
	}
	p[0] = c[0];
	p[1] = c[1];
	p[2] = c[2];
      }
    }
  }

  if (send_coverage) {
#ifndef NO_ZLIB
    write_image_to_file_gz(temp_scratchfile);
#else
    write_image_to_file(temp_scratchfile);
#endif /* NO_ZLIB */
    coverage_send_scratchfile();
  }

  if (!map_threshold || !map_flag) {
    /* map outline is already drawn; no need to fill in for coverage window. */
    return;
  }

  f = foreground;
  p = image->pixelPtr;
  drawWidth = h*w;
  c = outline_colors + PIXELSIZE;
  for (i = 0; i <drawWidth; i++, p+=PIXELSIZE) {
    fg = *(f++);
    if (fg == 1) {
      p[0] = c[0];
      p[1] = c[1];
      p[2] = c[2];
    }
  }
}

void
coverage_send_static_file(void)
{
  fprintf(gv_out, "(read geometry { define earth_h {\n %s \n} } )", static_description);
}

void
coverage_send_scratchfile(void)
{
  fprintf(gv_out, "(read geometry { define earth_h {\n %s \n} } )", dynamic_description);
}

/*
 * tracks_to_foreground - place dot on the foreground map which corresponds
 * to the subsatellite point (nadir).  Do this for every satellite
 * in the list.
 */
void
tracks_to_foreground(const Satellite_list SL, int projection, grid * g,
		     unsigned int color_index, const CentralBody * pcb)
{
  unsigned int color;
  Satellite_list sl = SL;

  while (sl) {
    if (sl->s->tag == 1) {
      /* inelegant choice of special color */
      color = SPECIAL_GROUND_TRACKS_CI;
    } else {
      color = color_index;
    }
    track_to_foreground(sl->s, projection, g, color, pcb);
    sl = sl->next;
  }
}

/*
 * track_to_foreground - routine to increment the foreground map at the point
 * underneath the satellite.  For a single satellite.
 *
 * grid_index = color index to insert into the foreground array
 */
void
track_to_foreground(Satellite s, int projection, grid * g,
		    unsigned int color_index, const CentralBody * pcb)
{
  LatLon l;
  int coords[2];

  spherical_to_lat_lon(&l, &(s->x_S), s->t, pcb);
  latlon_to_grid_index(coords, &l, projection, g);
  foreground[g->width * coords[1] + coords[0]] = color_index;

}

/*
 * Given lat/lon coordinates of point on central body, return corresponding
 * index into grid.
 *        grid_index[0]=column index, grid_index[1]=row index.
 */
static void
latlon_to_grid_index(int grid_index[2], LatLon * pl, int projection, grid * g)
{
  double proj[2];
  LatLon l;

  /*
   * The vertical center-line of the grid corresponds to longitude =
   * Longitude_Center_Line, whereas both 2-d projections place longitude = 0
   * along this line.  So shift the input longitude as required.
   */
  l.lat = pl->lat;
  l.lon = pl->lon - Longitude_Center_Line;

  if (l.lon < -180.0) {
    l.lon += 360.0;
  } else if (l.lon > 180.0) {
    l.lon -= 360.0;
  }

  switch (projection) {
  case UNPROJECTED:
    project_latlon_unprojected(proj, &l);
    break;
  case SINUSOIDAL:
    project_latlon_sinusoidal(proj, &l);
    break;
  case SPHERICAL:
    project_latlon_spherical(proj, &l);
    break;
  case CYLINDRICAL:
  default:
    project_latlon_cylindrical(proj, &l);
  }

  /*
   * It's assumed that proj[0] ranges from -180 to 180, and
   *                   proj[1] ranges from -90 to 90,  no matter what.
   */
  grid_index[0] = ((int) (0.5 + (proj[0] + 180.0) * (g->width) / 360.0));
  grid_index[1] = ((int) (0.5 + proj[1] * (g->height - 1) / 180.0));
}
