/*  File drawmap.c  */

#include <functions.h>
#include <devices/printer.h>
#include <errno.h>
#include <exec/io.h>
#include <exec/libraries.h>
#include <exec/memory.h>
#include <fcntl.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "reqbase.h"
#include "ILBM_lib.h"
#include "drawmap-menu.h"
#include "drawmap.h"
#include "drawmap-req.h"
#include "drawmap-help.h"
#include "drawmap-proto.h"

struct Screen *s;                      /* pointer to screen          */
struct Window *w;                      /* pointer to Window          */
struct RastPort *rp;                   /* pointer to RastPort        */
struct ViewPort *vp;                   /* pointer to ViewPort        */
struct TmpRas mapTmpRas;               /* temp raster for flood fill */
struct Library *GfxBase;
struct Library *IntuitionBase;
struct Remember *rememberkey;          /* pointer for cleaning up */

struct ReqLib *ReqBase;                /* address of requester library */
struct ExtendedColorRequester colorstruct;
struct ReqFileRequester filereq;
char filename[FCHARS];                 /* space for save file and */
char directoryname[DSIZE];             /*   dir name              */
char pathname[DSIZE+FCHARS];           /* full path and file name */

struct ILBMBase *ILBMBase;             /* address of ILBM library */
ILBMFrame IlbmFrame;

unsigned short *arrow, *cross;         /* storage for mouse pointers */
unsigned short *waiter, *transparent;

unsigned char *bp[DEPTH];              /* bitplane pointers */

struct Requester req;

struct BitMap map_bitmap;              /* holding area for initial flat map */
struct RastPort map_rp;                /* rastport for initial flat map */ 
char got_flat_map;

static long title_toggle = FALSE;

static char centered = TRUE;           /* begin with centered maps */

/* ============================================================= */

main (void)

{
   struct IntuiMessage *msg;
   PLANEPTR workspace;
   int ix;
   FILE *config;

   rememberkey = NULL;                 /* initialize rememberkey */
   if ((GfxBase = (struct Library *) OpenLibrary ("graphics.library", 0L))
        == NULL)  {
      printf ("Can't open graphics library\n");
      exit (10);
   }
   if ((IntuitionBase=(struct Library *) OpenLibrary ("intuition.library",0L))
        == NULL)  {
      printf ("Can't open intuition library\n");
      goto end1;
   }
   if ((ReqBase = (struct ReqLib *) OpenLibrary ("req.library", 0L))
        == NULL)  {
      printf ("Can't open req.library\n");
      goto end2;
   }
   if ((ILBMBase = (struct ILBMBase *) OpenLibrary ("ilbm.library", 0L))
        == NULL)  {
      printf ("Can't open ilbm.library\n");
      goto end3;
   }
   if ((s = (struct Screen *) OpenScreen (&mapscreen)) == NULL)  {
      printf ("Can't open screen\n");
      goto end4;
   }
   mapWindow.Screen = s;
   newhelpw.Screen = s;
   if ((w = (struct Window *) OpenWindow (&mapWindow)) == NULL)  {
      printf ("Can't open window\n");
      goto end5;
   }
                                       /* load default color table */
   CopyMem ((char *) mapcolors, (char *) configcolors,
            sizeof(mapcolors));
   if ((config = fopen (configfile, "rb")) != NULL)  {
      if ((ix = fread ((char *) &configcolors[0], sizeof (char),
                       sizeof(configcolors), config))
                       != sizeof(configcolors))  {
         CopyMem ((char *) mapcolors, (char *) configcolors,
                  sizeof(mapcolors));
      }
      fclose (config);
   }
   vp = &(s->ViewPort);                /* pointer to viewport */
   LoadRGB4 (vp, &configcolors[0], NUM_COLORS); /* init. color values  */
                                       /* init. mouse pointers */
   arrow = (UWORD *) AllocRemember (&rememberkey, ARROW_SIZE, MEMF_CHIP);
   CopyMem ((char *) arrow_data, (char *) arrow, ARROW_SIZE);

   cross = (UWORD *) AllocRemember (&rememberkey, CROSS_SIZE, MEMF_CHIP);
   CopyMem ((char *) cross_data, (char *) cross, CROSS_SIZE);
   
   waiter = (UWORD *) AllocRemember (&rememberkey, WAITER_SIZE, MEMF_CHIP);
   CopyMem ((char *) waiter_data, (char *) waiter, WAITER_SIZE);
   
   transparent = (UWORD *) AllocRemember (&rememberkey, TRANSPARENT_SIZE,
                                          MEMF_CHIP);
   CopyMem ((char *) transparent_data, (char *) transparent,
            TRANSPARENT_SIZE);

   SetPointer (w, waiter, WAITER_SIZE/4-2, 16, WAITER_X_OFFSET,
               WAITER_Y_OFFSET);

   rp = w->RPort;
   if ((workspace = (PLANEPTR) AllocRaster (WWIDTH,WHEIGHT)) == NULL)  {
      printf ("No space for Temporary Raster\n");
      goto end6;
   }

   InitBitMap (&map_bitmap, DEPTH, WWIDTH, WHEIGHT);
   InitRastPort (&map_rp);
   map_rp.BitMap = &map_bitmap;

   for (ix=0; ix<DEPTH; ++ix)          /* initialize flat map storage area */
      map_bitmap.Planes[ix] = NULL;
   for (ix=0; ix<DEPTH; ++ix)  {
      map_bitmap.Planes[ix] = (PLANEPTR) AllocRaster (WWIDTH, WHEIGHT);
      if (map_bitmap.Planes[ix] == NULL)  {
         printf ("No space for bitmap workspace\n");
         goto end7;
      }
   }                                   /* temp raster for flood fill */
   InitTmpRas (&mapTmpRas, workspace, RASSIZE(WWIDTH,WHEIGHT));
   rp->TmpRas = &mapTmpRas;            /* link it to the RastPort */

   if ((ix = readmap ()) != OK)  {     /* read map files */
      printf ("Error reading map files\n");
      goto end7;
   }
   if ((ix = get_min_max ()) != OK)  {
      printf ("Map files are corrupted\n");
      goto end7;
   }

   if ((ix = init_helpitems ()) != OK)
      printf ("\nUnable to initialize help information, but continuing\n");

   loadmappic();                       /* load initial flat map */

   for (ix=0; ix<DEPTH; ++ix)          /* initialize bitplane pointers */
      bp[ix] = rp->BitMap->Planes[ix];

   init_requesters ();                 /* initialize the requesters */

   SetMenuStrip (w, &menu[0]);         /* bring up the menus */

   SetAPen (rp, ORANGE);               /* set initial drawing pens */
   SetBPen (rp, BLUE);
   SetDrMd (rp, JAM2);

   view_height = VIEW_HEIGHT;          /* constants for globe view */
   eta = view_height/RE;
   facp = 1. + eta;
   etap = 1./facp;

   set_rotation_matrices (0., 0.);     /* initialize rotation matrices */

   SetPointer (w, arrow, ARROW_SIZE/4-2, 16, ARROW_X_OFFSET, ARROW_Y_OFFSET);

   while (1)  {                        /* wait for message from */
      WaitPort ( w->UserPort );        /*   Intuition           */
      if ((msg = (struct IntuiMessage *) GetMsg (w->UserPort)) == NULL)
         continue;
      else if ((ix = handle_event (msg)) != OK)
         break;
      else
         ReplyMsg (msg);
   }
   ReplyMsg (msg);
   if (helpbuffer != NULL)
      free (helpbuffer);
   PurgeFiles (&filereq);              /* clean up after file requester */
end7:
   for (ix=0; ix<DEPTH; ++ix)
      if (map_bitmap.Planes[ix] != NULL)
         FreeRaster (map_bitmap.Planes[ix], WWIDTH, WHEIGHT);
   FreeRaster (workspace, WWIDTH, WHEIGHT);
end6:
   FreeRemember (&rememberkey, TRUE);  /* clean up */
   ClearPointer (w);
   ClearMenuStrip (w);
   Forbid ();                          /* strip remaining messages */
   while (1)  {
      if ((msg = (struct IntuiMessage *) GetMsg (w->UserPort)) == NULL)
         break;
      else
         ReplyMsg (msg);
   }
   ModifyIDCMP (w, NULL);
   Permit ();
   CloseWindow (w);
end5:
   CloseScreen (s);
end4:
   CloseLibrary (ILBMBase);
end3:
   CloseLibrary (ReqBase);
end2:
   CloseLibrary (IntuitionBase);
end1:
   CloseLibrary (GfxBase);
}

/* ============================================================= */

int handle_event (struct IntuiMessage *msg)
                                       /* processes main Intuition events */
{
   static char box_error[]               = "Box of zero size not allowed";
   static char *dlevels[]                = { "High", "Medium high",
                                             "Medium", "Medium low",
                                             "Low" };
   static char drag_prompt[]             = "Press and drag left button to "
                                           "select box, right button to "
                                           "abort";
   static char expandbox_error[]         = "Invalid map displayed for Box "
                                           "Zoom Out option";
   static char flood_wait[]              = "Press left button to select area "
                                           "to fill, right button to abort";
   static char fmt_DETAIL_LEVEL_TYPE[]   = "%s detail level";
   static char fmt_DISPLAY_CENTER_TYPE[] = "Center of map is at "
                                           "latitude = %.3lf deg, "
                                           "longitude = %.3lf deg";
   static char fmt_ORBITAL_TYPE[]        = "Orbital...view from %.2lf "
                                           "kilometers";
   static char fmt_ZOOM_IN_TYPE[]        = "Zoom In...view from %.2lf "
                                           "kilometers";
   static char fmt_ZOOM_OUT_TYPE[]       = "Zoom Out...view from %.2lf "
                                           "kilometers";
   static char grid_error[]              = "Invalid map displayed for Grid "
                                           "option";
   static char lines_wait[]              = "Press left button to draw lines, "
                                           "right button to abort";
   static char press_prompt[]            = "Press left button to select "
                                           "center point, right button to "
                                           "abort";
   static char print_abort[]             = "Printing aborted";
   static char print_error[]             = "Printer error";
   static char print_wait[]              = "Printing...press right button to "
                                           "abort";
   static char smallbox_error[]          = "Invalid map displayed for Box "
                                           "Zoom In option";
   static char title_AITOFF_TYPE[]       = "Aitoff Polyconic Projection";
   static char title_BOX_TYPE[]          = "Box";
   static char title_BOX_ZOOM_IN_TYPE[]  = "Box Zoom In";
   static char title_BOX_ZOOM_OUT_TYPE[] = "Box Zoom Out";
   static char title_CLEARS_TYPE[]       = "Clear Screen";
   static char title_COAST_COLOR_TYPE[]  = "Color for Coastlines";
   static char title_COUNTRY_COLOR_TYPE[] = "Color for Countries";
   static char title_DRAW_LINE_TYPE[]    = "Draw Lines";
   static char title_FLAT_TYPE[]         = "Flat (Cylindrical Equidistant) "
                                           "Projection";
   static char title_FLOOD_COLOR_TYPE[]  = "Color for Flood Fill";
   static char title_FLOOD_TYPE[]        = "Flood Fill";
   static char title_GALL_TYPE[]         = "Gall Stereographic Projection";
   static char title_GLOBE_TYPE[]        = "Globe (Orthographic Projection)"
                                           "...view from infinitely far "
                                           "away";
   static char title_HAMMER_AITOFF_TYPE[] = "Hammer-Aitoff Polyconic "
                                            "Projection";
   static char title_HELP_TYPE[]         = "Display help file";
   static char title_HELP_TYPE_error[]   = "Error displaying help information";
   static char title_ISLAND_COLOR_TYPE[] = "Color for Islands";
   static char title_LAKE_COLOR_TYPE[]   = "Color for Lakes";
   static char title_LAMBERT_TYPE[]      = "Lambert Cylindrical Projection";
   static char title_LINE_COLOR_TYPE[]   = "Color for lines";
   static char title_MERCATOR_TYPE[]     = "Mercator Projection";
   static char title_MOLLWEIDE_TYPE[]    = "Mollweide Projection";
   static char title_PALETTE_TYPE[]      = "Modify Color Palette";
   static char title_PRINT_TYPE[]        = "Print Screen";
   static char title_REDRAW_TYPE[]       = "Redraw";
   static char title_REDRAW_TYPE_error[] = "Invalid map displayed for Redraw "
                                           "option";
   static char title_RESET_COLOR_TYPE[]  = "Reset Colors";
   static char title_RIVER_COLOR_TYPE[]  = "Color for Rivers";
   static char title_SANSON_TYPE[]       = "Sanson-Flamsteed Projection";
   static char title_SHADOW_TYPE[]       = "Shadow";
   static char title_SIMPLE_POLYCONIC_TYPE[] = "Simple Polyconic Projection";
   static char title_STATE_COLOR_TYPE[]  = "Color for States";
   static char title_TEXT_COLOR_TYPE[]   = "Color for text";
   static char title_TEXT_TYPE[]         = "Text";
   static char title_abort[]             = "Selection aborted";
   static char title_buffer[]            = "                                "
                                           "                                ";
   static char title_config_not_saved[]  = "Configuration not saved";
   static char title_config_saved[]      = "Configuration saved to disk";
   static char title_not_saved[]         = "Screen not saved";
   static char title_saved[]             = "Screen saved to disk";
   static char first_time = TRUE;
   static long oldtype = -1L;
   static long flood_color = DK_GRN;
   static long line_color  = LT_YEL;
   static long text_color  = WHITE;
   static double latg = 0.;
   static double lamg = 0.;
   static double lat[2], lam[2];
   struct IntuiMessage *msgf;
   FILE *config;
   char *p, abort;
   unsigned short select;
   int i, result;
   long x, y, x0, y0;
   long menunum, itemnum, subnum, type;
   double latb[2], lamb[2], del, av;

   disable_menus (IDCMPFLAGS);         /* disable menus while do this event */
   abort = FALSE;
   if (first_time)  {
      first_time = FALSE;
      if (got_flat_map == OK)
         oldtype = FLAT_TYPE;
   }
   switch (msg->Class) {
   case MENUPICK:
      select = msg->Code;
      if (select == MENUNULL)          /* no extended selections */
         break;
      menunum = MENUNUM (select);
      itemnum = ITEMNUM (select);
      subnum = SUBNUM (select);        /* build unique id for this */
      type = (long) (100*menunum+itemnum)*100+subnum; /* menu selection */
      switch (menunum)  {
      case PROJECT:
         switch (itemnum)  {
         case HELP:
            SetWindowTitles (w, 0, title_HELP_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            ModifyIDCMP (w, CLOSEWINDOW);
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            if ((result = displayhelp ()) != OK)  {
               SetWindowTitles (w, 0, title_HELP_TYPE_error);
               DisplayBeep (0);
            }
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            ModifyIDCMP (w, IDCMPFLAGS);
            break;
         case SAVEIT:                  /* save map to disk */
            ModifyIDCMP (w, CLOSEWINDOW);
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            if ((result = save_to_disk ()) != OK)  {
               SetWindowTitles (w, 0, title_not_saved);
               DisplayBeep (0);
            }
            else
               SetWindowTitles (w, 0, title_saved);
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            ModifyIDCMP (w, IDCMPFLAGS);
            break;
         case SAVE_CONFIG:             /* save configuration to disk */
            ModifyIDCMP (w, CLOSEWINDOW);
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            if ((config = fopen (configfile, "wb")) != NULL)  {
               if ((i = fwrite ((char *) &configcolors[0], sizeof (char),
                        sizeof(configcolors), config))
                    == sizeof(configcolors))
                  SetWindowTitles (w, 0, title_config_saved);
               else  {
                  SetWindowTitles (w, 0, title_config_not_saved);
                  DisplayBeep (0);
               }
               fclose (config);
            }
            else  {
               SetWindowTitles (w, 0, title_config_not_saved);
               DisplayBeep (0);
            }
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            ModifyIDCMP (w, IDCMPFLAGS);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case PRINT:                   /* print map */
            SetWindowTitles (w, 0, print_wait);
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            if ((result = printmap (w)) == OK)
               SetWindowTitles (w, 0, title_PRINT_TYPE);
            else  {
               if (result == NOT_OK)
                  SetWindowTitles (w, 0, print_error);
               else
                  SetWindowTitles (w, 0, print_abort);
               DisplayBeep (0);
            }
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            break;
         case CLEARS:                  /* clear screen */
            SetWindowTitles (w, 0, title_CLEARS_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            SetRast (rp, BLUE);
            oldtype = type;
            break;
         case QUIT:                    /* close up shop */
            enable_menus ();
            return (NOT_OK);
            break;
         default:
            break;
         }
         break;
      case BOUNDARIES:
         switch (itemnum)  {
         case COASTLINES:
         case COUNTRIES:
         case STATES:
         case ISLANDS:
         case LAKES:
         case RIVERS:
            map[itemnum].plot ^= 1;
            break;
         case DETAIL_LEVEL:
            detail_level = subnum + 1;
            sprintf (title_buffer, fmt_DETAIL_LEVEL_TYPE,
               dlevels[subnum]);
            SetWindowTitles (w, 0, title_buffer);
            break;
         default:
            break;
         }
         break;
      case MAPS:                       /* maps menu */
         switch (itemnum)  {
         case CYLINDRICAL:
            switch (subnum)  {
            case FLAT:                 /* flat map */
            case MERCATOR:             /* Mercator map */
            case LAMBERT:              /* Lambert's cylindrical map */
            case GALL:                 /* Gall's stereographic map */
               ModifyIDCMP (w, CLOSEWINDOW);
               SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                           WAITER_X_OFFSET, WAITER_Y_OFFSET);
               if (type==FLAT_TYPE)
                  p = title_FLAT_TYPE;
               else if (type==MERCATOR_TYPE)
                  p = title_MERCATOR_TYPE;
               else if (type==LAMBERT_TYPE)
                  p = title_LAMBERT_TYPE;
               else if (type==GALL_TYPE)
                  p = title_GALL_TYPE;
               SetWindowTitles (w, 0, p);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               fullmap (type);
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                           ARROW_X_OFFSET, ARROW_Y_OFFSET);
               ModifyIDCMP (w, IDCMPFLAGS);
               oldtype = type;
               break;
            default:
               break;
            }
            break;
         case SPHERE:
            switch (subnum)  {
            case ZOOM_IN:              /* zoom views */
            case ZOOM_OUT:
               if (type==ZOOM_IN_TYPE)  {
                  view_height /= 2.;
                  if (view_height<MIN_HEIGHT)
                     view_height = MIN_HEIGHT;
                  sprintf (title_buffer, fmt_ZOOM_IN_TYPE, view_height);
               }
               else  {
                  view_height *= 2.;
                  sprintf (title_buffer, fmt_ZOOM_OUT_TYPE, view_height);
               }
               eta = view_height/RE;
               facp = 1. + eta;
               etap = 1./facp;
            case GLOBE:                /* globe views */
            case ORBITAL:
               if (type==ORBITAL_TYPE)  {
                  x = msg->MouseX;
                  y = msg->MouseY;
                  get_user_input (ORBITAL_TYPE, x, y, &view_height,
                                  &x0, &y0);
                  eta = view_height/RE;   /* initialize values for */
                  facp = 1. + eta;        /*   orbital view        */
                  etap = 1./facp;
                  sprintf (title_buffer, fmt_ORBITAL_TYPE, view_height);
                  SetWindowTitles (w, 0, title_buffer);
               }
               else if (type==GLOBE_TYPE)
                  SetWindowTitles (w, 0, title_GLOBE_TYPE);
               else
                  SetWindowTitles (w, 0, title_buffer);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               ModifyIDCMP (w, CLOSEWINDOW);
               SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                           WAITER_X_OFFSET, WAITER_Y_OFFSET);
               globe (latg, lamg, type); /* draw map */
               stars ();
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                           ARROW_X_OFFSET, ARROW_Y_OFFSET);
               ModifyIDCMP (w, IDCMPFLAGS);
               oldtype = type;
               break;
            default:
               break;
            }
            break;
         case POLYCONIC:
            switch (subnum)  {
            case AITOFF:               /* Aitoff map */
            case HAMMER_AITOFF:        /* Hammer-Aitoff map */
            case SIMPLE_POLYCONIC:     /* simple polyconic map */
               ModifyIDCMP (w, CLOSEWINDOW);
               SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                           WAITER_X_OFFSET, WAITER_Y_OFFSET);
               if (type==AITOFF_TYPE)
                  p = title_AITOFF_TYPE;
               else if (type==HAMMER_AITOFF_TYPE)
                  p = title_HAMMER_AITOFF_TYPE;
               else if (type==SIMPLE_POLYCONIC_TYPE)
                  p = title_SIMPLE_POLYCONIC_TYPE;
               SetWindowTitles (w, 0, p);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               fullmap (type);
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                           ARROW_X_OFFSET, ARROW_Y_OFFSET);
               ModifyIDCMP (w, IDCMPFLAGS);
               oldtype = type;
               break;
            default:
               break;
            }
            break;
         case PSEUDO_CYLINDRICAL:
            switch (subnum)  {
            case MOLLWEIDE:            /* Mollweide projection */
            case SANSON:               /* Sanson-Flamsteed projection */
               ModifyIDCMP (w, CLOSEWINDOW);
               SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                           WAITER_X_OFFSET, WAITER_Y_OFFSET);
               if (type==MOLLWEIDE_TYPE)
                  p = title_MOLLWEIDE_TYPE;
               else if (type==SANSON_TYPE)
                  p = title_SANSON_TYPE;
               SetWindowTitles (w, 0, p);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               fullmap (type);
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                           ARROW_X_OFFSET, ARROW_Y_OFFSET);
               ModifyIDCMP (w, IDCMPFLAGS);
               oldtype = type;
               break;
            default:
               break;
            }
            break;
         case BOXES:
            switch (subnum)  {
            case BOX_ZOOM_IN:          /* box zoom in */
               if (oldtype != BOX_TYPE && oldtype != BOX_ZOOM_IN_TYPE &&
                   oldtype != BOX_ZOOM_OUT_TYPE)  {
                  SetWindowTitles (w, 0, smallbox_error);
                  ShowTitle (s, TRUE);
                  title_toggle = TRUE;
                  DisplayBeep (0);
                  break;
               }                       /* and fall through to  */
            case BOX:                  /* ordinary box request */
               SetWindowTitles (w, 0, drag_prompt);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               if (type == BOX_TYPE &&
                   ((oldtype!=FLAT_TYPE && oldtype!=MERCATOR_TYPE)) ||
                   ((!centered) && (oldtype==FLAT_TYPE ||
                                    oldtype==MERCATOR_TYPE)))  {
                  ModifyIDCMP (w, CLOSEWINDOW);
                  SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                              WAITER_X_OFFSET, WAITER_Y_OFFSET);
                  showmappic ();
                  latg = lamg = 0.;
                  set_rotation_matrices (latg, lamg);
                  oldtype = FLAT_TYPE;
                  ModifyIDCMP (w, IDCMPFLAGS);
               }
               SetPointer (w, cross, CROSS_SIZE/4-2, 16,
                           CROSS_X_OFFSET, CROSS_Y_OFFSET);
               if ((result = getbox (&x0, &y0, &x, &y)) != OK)  {
                  SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                              ARROW_X_OFFSET, ARROW_Y_OFFSET);
                  if (result == ABORT)
                     SetWindowTitles (w, 0, title_abort);
                  else
                     SetWindowTitles (w, 0, box_error);
                  DisplayBeep (0);
                  break;
               }
               if (type == BOX_TYPE)
                  SetWindowTitles (w, 0, title_BOX_TYPE);
               else
                  SetWindowTitles (w, 0, title_BOX_ZOOM_IN_TYPE);
               ModifyIDCMP (w, CLOSEWINDOW);
               SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                           WAITER_X_OFFSET, WAITER_Y_OFFSET);
               if (type == BOX_TYPE)  {
                  getcoord (x0, y0, oldtype, &lat[0], &lam[0]);
                  getcoord (x, y, oldtype, &lat[1], &lam[1]);
               }
               else  {
                  getcoord_box (x0, y0, lat, lam, &latb[0], &lamb[0]);
                  getcoord_box (x, y, lat, lam, &latb[1], &lamb[1]);
                  lat[0] = latb[0];
                  lat[1] = latb[1];
                  lam[0] = lamb[0];
                  lam[1] = lamb[1];
               }
               if (lam[0] < 0.)  {     /* make limits positive */
                  lam[0] += 360.;
                  lam[1] += 360.;
               }
               box (lat, lam);
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                           ARROW_X_OFFSET, ARROW_Y_OFFSET);
               ModifyIDCMP (w, IDCMPFLAGS);
               oldtype = type;
               break;
            case BOX_ZOOM_OUT:         /* box zoom out */
               if (oldtype != BOX_TYPE && oldtype != BOX_ZOOM_IN_TYPE &&
                   oldtype != BOX_ZOOM_OUT_TYPE)  {
                  SetWindowTitles (w, 0, expandbox_error);
                  ShowTitle (s, TRUE);
                  title_toggle = TRUE;
                  DisplayBeep (0);
                  break;
               }
               SetWindowTitles (w, 0, title_BOX_ZOOM_OUT_TYPE);
               ModifyIDCMP (w, CLOSEWINDOW);
               SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                           WAITER_X_OFFSET, WAITER_Y_OFFSET);
               del = EXPAND_SCALE_FACTOR * (lat[0] - lat[1])/2.;
               av = (lat[0] + lat[1])/2.;
               lat[0] = av + del;
               lat[1] = av - del;
               if (lat[0] > 90.)
                  lat[0] = 90.;
               if (lat[1] < -90.)
                  lat[1] = -90.;
               del = EXPAND_SCALE_FACTOR * (lam[1] - lam[0])/2.;
               if (del > 180.)         /* disallow more than a full map */
                  del = 180.;
               av = (lam[0] + lam[1])/2.;
               lam[0] = av - del;
               lam[1] = av + del;
               if (lam[0] < 0.)  {     /* make limits positive */
                  lam[0] += 360.;
                  lam[1] += 360.;
               }
               box (lat, lam);
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                           ARROW_X_OFFSET, ARROW_Y_OFFSET);
               ModifyIDCMP (w, IDCMPFLAGS);
               oldtype = type;
               break;
            default:
               break;
            }
            break;
         case REDRAW:                  /* redraw previous map */
            if (oldtype != FLAT_TYPE    && oldtype != MERCATOR_TYPE    &&
                oldtype != LAMBERT_TYPE && oldtype != GALL_TYPE        &&
                oldtype != HAMMER_AITOFF_TYPE    && 
                oldtype != SIMPLE_POLYCONIC_TYPE &&
                oldtype != AITOFF_TYPE           &&
                oldtype != MOLLWEIDE_TYPE        &&
                oldtype != SANSON_TYPE           &&
                oldtype != GLOBE_TYPE   && oldtype != ORBITAL_TYPE     &&
                oldtype != ZOOM_IN_TYPE && oldtype != ZOOM_OUT_TYPE    &&
                oldtype != BOX_TYPE     && oldtype != BOX_ZOOM_IN_TYPE &&
                oldtype != BOX_ZOOM_OUT_TYPE)  {
               SetWindowTitles (w, 0, title_REDRAW_TYPE_error);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               DisplayBeep (0);
               break;
            }
            SetWindowTitles (w, 0, title_REDRAW_TYPE);
            ModifyIDCMP (w, CLOSEWINDOW);
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            if (oldtype == FLAT_TYPE    || oldtype == MERCATOR_TYPE ||
                oldtype == LAMBERT_TYPE || oldtype == GALL_TYPE     ||
                oldtype == HAMMER_AITOFF_TYPE    || 
                oldtype == SIMPLE_POLYCONIC_TYPE ||
                oldtype == AITOFF_TYPE           ||
                oldtype == MOLLWEIDE_TYPE        ||
                oldtype == SANSON_TYPE)
               fullmap (oldtype);
            else if (oldtype == ZOOM_IN_TYPE || oldtype == ZOOM_OUT_TYPE ||
                     oldtype == GLOBE_TYPE   || oldtype == ORBITAL_TYPE)  {
               globe (latg, lamg, oldtype);
               stars ();
            }
            else
               box (lat, lam);
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            ModifyIDCMP (w, IDCMPFLAGS);
            break;
         case CENTER:                  /* set center of map displays */
            switch (subnum)  {
            case DISPLAY_CENTER:       /* show coords of map center */
               sprintf (title_buffer, fmt_DISPLAY_CENTER_TYPE, latg, lamg);
               SetWindowTitles (w, 0, title_buffer);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               break;
            case SLIDE:                /* slide center along equator */
            case NEW_CENTER:           /* pick new map center point */
               SetWindowTitles (w, 0, press_prompt);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               if (oldtype!=FLAT_TYPE || (!centered))  {
                  ModifyIDCMP (w, CLOSEWINDOW);
                  SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                              WAITER_X_OFFSET, WAITER_Y_OFFSET);
                  showmappic ();
                  latg = lamg = 0.;
                  set_rotation_matrices (latg, lamg);
                  ModifyIDCMP (w, IDCMPFLAGS);
               }
               grid (FLAT_TYPE, latg, lamg);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               SetPointer (w, cross, CROSS_SIZE/4-2, 16,
                           CROSS_X_OFFSET, CROSS_Y_OFFSET);
               while (1)  {
                  WaitPort (w->UserPort);
                  if ((msgf = (struct IntuiMessage *) GetMsg (w->UserPort))
                      == NULL)
                     continue;
                  else if (msgf->Code == SELECTDOWN)
                     break;
                  else if (msgf->Code == MENUDOWN)  {
                     abort = TRUE;
                     ReplyMsg (msgf);
                     SetWindowTitles (w, 0, title_abort);
                     SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                                 ARROW_X_OFFSET, ARROW_Y_OFFSET);
                     DisplayBeep (0);
                     break;
                  }
                  else
                     ReplyMsg (msgf);
               }
               if (abort)
                  break;
               x = msgf->MouseX;
               y = msgf->MouseY;
               ReplyMsg (msgf);
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                           ARROW_X_OFFSET, ARROW_Y_OFFSET);
               getcoord (x, y, FLAT_TYPE, &latg, &lamg);
               if (type==SLIDE_TYPE)
                  latg = 0.;
               set_rotation_matrices (latg, lamg);
               sprintf (title_buffer, fmt_DISPLAY_CENTER_TYPE, latg, lamg);
               SetWindowTitles (w, 0, title_buffer);
               break;
            case RECENTER:             /* move map center to 0,0 */
               latg = lamg = 0.;
               set_rotation_matrices (latg, lamg);
               sprintf (title_buffer, fmt_DISPLAY_CENTER_TYPE, latg, lamg);
               SetWindowTitles (w, 0, title_buffer);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               break;
            default:
               break;
            }
            break;
         default:
            break;
         }
         break;
      case COLORS:                     /* colors menu */
         switch (itemnum)  {
         case PALETTE:                 /* modify palette */
            SetWindowTitles (w, 0, title_PALETTE_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            ModifyIDCMP (w, CLOSEWINDOW);
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            result = ExtendedColorRequester (&colorstruct);
            for (i=0; i<NUM_COLORS; ++i)
               configcolors[i] = GetRGB4 (vp->ColorMap, i);
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            ModifyIDCMP (w, IDCMPFLAGS);
            break;
         case RESET_COLOR:             /* load default color table */
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            LoadRGB4 (vp, &mapcolors[0], NUM_COLORS);
            CopyMem ((char *) mapcolors, (char *) configcolors,
                     sizeof(mapcolors));
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            SetWindowTitles (w, 0, title_RESET_COLOR_TYPE);
            break;
         case FLOOD_COLOR:             /* select color for flood fill */
         case LINE_COLOR:              /* select color for lines */
         case TEXT_COLOR:              /* select color for text */
            if (itemnum==FLOOD_COLOR)  {
               flood_color = subnum;
               p = title_FLOOD_COLOR_TYPE;
            }
            else if (itemnum==LINE_COLOR)  {
               line_color = subnum;
               p = title_LINE_COLOR_TYPE;
            }
            else if (itemnum==TEXT_COLOR)  {
               text_color = subnum;
               p = title_TEXT_COLOR_TYPE;
            }
            SetWindowTitles (w, 0, p);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case COAST_COLOR:
            map[COASTLINES].color = subnum;
            SetWindowTitles (w, 0, title_COAST_COLOR_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case COUNTRY_COLOR:
            map[COUNTRIES].color = subnum;
            SetWindowTitles (w, 0, title_COUNTRY_COLOR_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case STATE_COLOR:
            map[STATES].color = subnum;
            SetWindowTitles (w, 0, title_STATE_COLOR_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case ISLAND_COLOR:
            map[ISLANDS].color = subnum;
            SetWindowTitles (w, 0, title_ISLAND_COLOR_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case LAKE_COLOR:
            map[LAKES].color = subnum;
            SetWindowTitles (w, 0, title_LAKE_COLOR_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case RIVER_COLOR:
            map[RIVERS].color = subnum;
            SetWindowTitles (w, 0, title_RIVER_COLOR_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         default:
            break;
         }
         break;
      case EDIT:                       /* edit menu */
         switch (itemnum)  {
         case GRID:
            if (oldtype!=FLAT_TYPE     && oldtype!=MERCATOR_TYPE &&
                oldtype!=LAMBERT_TYPE  && oldtype!=GALL_TYPE     &&
                oldtype!=HAMMER_AITOFF_TYPE    && 
                oldtype!=SIMPLE_POLYCONIC_TYPE &&
                oldtype!=AITOFF_TYPE           &&
                oldtype!=MOLLWEIDE_TYPE        &&
                oldtype!=SANSON_TYPE           &&
                oldtype!=GLOBE_TYPE    && oldtype!=ORBITAL_TYPE  &&
                oldtype!=ZOOM_IN_TYPE  && oldtype!=ZOOM_OUT_TYPE)  {
               SetWindowTitles (w, 0, grid_error);
               ShowTitle (s, TRUE);
               title_toggle = TRUE;
               DisplayBeep (0);
            }
            else  {
               ModifyIDCMP (w, CLOSEWINDOW);
               SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                           WAITER_X_OFFSET, WAITER_Y_OFFSET);
               grid (oldtype, latg, lamg);
               SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
               ModifyIDCMP (w, IDCMPFLAGS);
            }
            break;
         case FLOOD:
            SetWindowTitles (w, 0, flood_wait);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            SetPointer (w, cross, CROSS_SIZE/4-2, 16,
                        CROSS_X_OFFSET, CROSS_Y_OFFSET);
            if ((result = floodfill (flood_color)) == OK)
               SetWindowTitles (w, 0, title_FLOOD_TYPE);
            else  {
               SetWindowTitles (w, 0, title_abort);
               DisplayBeep (0);
            }
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            break;
         case DRAW_LINE:               /* draw connected lines */
            SetWindowTitles (w, 0, lines_wait);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            SetPointer (w, cross, CROSS_SIZE/4-2, 16,
                        CROSS_X_OFFSET, CROSS_Y_OFFSET);
            if ((result = draw_line (line_color)) == OK)
               SetWindowTitles (w, 0, title_DRAW_LINE_TYPE);
            else  {
               SetWindowTitles (w, 0, title_abort);
               DisplayBeep (0);
            }
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            break;
         case SHADOW:                  /* make shadows */
            ModifyIDCMP (w, CLOSEWINDOW);
            ShowTitle (s, FALSE);      /* don't shadow the title */
            SetPointer (w, waiter, WAITER_SIZE/4-2, 16,
                        WAITER_X_OFFSET, WAITER_Y_OFFSET);
            shadow ();
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            SetWindowTitles (w, 0, title_SHADOW_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            ModifyIDCMP (w, IDCMPFLAGS);
            break;
         case TEXT:                    /* get user text */
            x = msg->MouseX;
            y = msg->MouseY;
            if ((result = do_text (x, y, text_color)) == ABORT)  {
               SetWindowTitles (w, 0, title_abort);
               DisplayBeep (0);
            }
            else
               SetWindowTitles (w, 0, title_TEXT_TYPE);
            ShowTitle (s, TRUE);
            title_toggle = TRUE;
            break;
         case TTOGGLE:                 /* toggle screen title bar */
            title_toggle ^= 1L;
            ShowTitle (s, title_toggle);
            break;
         default:
            break;
         }
         break;
      default:
         break;
      }
      break;
   default:
      break;
   }
   enable_menus ();                    /* re-enable menu events */
   return (OK);
}

/* ================================================================ */

void box (double *latp, double *lamp)  /* draws areas contained within */
                                       /*   a rectangular region       */
{
   char first, prev_in_view, in_view, add360, prev_add360;
   short h1, h1c, h1prev;              /* x-dist. (pix) from center */
   short h2, h2c, h2prev;              /* y-dist. (pix) from center */
   int i, na, nc;
   int lat1i, lat2i, lam1i, lam2i;
   long x, y;
   double lam, lamc, lamprev;          /* longitude */
   double lat, latc, latprev;          /* latitude  */
   double bwidth, bheight, bcx, bcy, xscale, yscale;
   double lat1, lat2, lam1, lam2;
   struct Arc *seg;
   struct Pt *pt;

   lat1 = latp[0];                     /* store values for box corners */
   lat2 = latp[1];                     /*   locally                    */
   lam1 = lamp[0];
   lam2 = lamp[1];
   lat1i = 60.*lat1;                   /* convert to signed minutes */
   lat2i = 60.*lat2;
   lam1i = 60.*lam1;
   lam2i = 60.*lam2;
   bwidth = lam2 - lam1;               /* box width (degrees)         */
   bheight = lat1 - lat2;              /* box height (degrees)        */
   bcx = (lam1 + lam2)/2.;             /* x-coord of box center (deg) */
   bcy = (lat1 + lat2)/2.;             /* y-coord of box center (deg) */
   xscale = WWIDTH / bwidth;           /* horizontal scale (pix/deg)  */
   yscale = WHEIGHT / bheight;         /* vertical scale (pix/deg)    */
   SetRast (rp, BLUE);                 /* blue background */
   SetAPen (rp, ORANGE);               /* outline the box */
   drawbox (0, 0, WWIDTH-1, WHEIGHT-1);
   for (nc=0; nc<NUM_MAPS; ++nc)  {    /* do each map type */
      if (map[nc].plot != TRUE)        /* if not requested, skip */
         continue;
      SetAPen (rp, map[nc].color);     /* initialize color */
      seg = map[nc].seg;
      pt = map[nc].pt;                 /* do each segment */
      for (na=0; na<map[nc].nsegs; ++na)  {
         first = TRUE;                 /* skip if not in view */
         if (seg[na].lat_min > lat1i || seg[na].lat_max < lat2i)
            continue;
         in_view = FALSE;
         if (seg[na].lam_max>=lam1i && seg[na].lam_min<=lam2i)
            in_view = TRUE;
         if ((seg[na].lam_max+(360*60))>=lam1i &&
             (seg[na].lam_min+(360*60))<=lam2i)
            in_view = TRUE;
         if (!in_view)
            continue;
         for (i=seg[na].first; i<=seg[na].last; ++i)  {
            if (pt[i].code < detail_level) /* filter by detail level */
               continue;
            lat = pt[i].lat;           /* latitude */
            lat /= 60.;
            lam = pt[i].lam;           /* longitude */
            lam /= 60.;
            in_view = add360 = FALSE;  /* get status of current point */
            if (lat<=lat1 && lat>=lat2)  {
               if (lam>=lam1 && lam<=lam2)
                  in_view = TRUE;
               if ((lam+360.)>=lam1 && (lam+360.)<=lam2)  {
                  in_view = add360 = TRUE;
                  lam += 360.;
               }
               if (in_view)  {
                  h1 = (lam-bcx) * xscale;
                  h2 = -(lat-bcy) * yscale;
               }
            }
            if (first)  {              /* check first point */
               first = FALSE;
               if (in_view)  {         /* if first point is in view, */
                  x = h1 + CENTERX;    /*   move pen to first point  */
                  y = h2 + CENTERY;    /*   and plot it              */
                  Move (rp, x, y);
                  WritePixel (rp, x, y);
                  h1prev = h1;
                  h2prev = h2;
               }
               prev_in_view = in_view; /* save status of first point */
               prev_add360 = add360;
               latprev = lat;
               lamprev = lam;
               continue;
            }
            if (in_view)  {
               if (!prev_in_view)  {   /* prev. point was not in view */
                  box_rim_point (lat, lam, latprev, lamprev, lat1, lam1,
                                 lat2, lam2, add360, prev_add360,
                                 &latc, &lamc);
                  h1c = (lamc-bcx) * xscale;
                  h2c = - (latc-bcy) * yscale;
                  x = h1c + CENTERX;   /* move to rim point & plot it */
                  y = h2c + CENTERY;
                  Move (rp, x, y);
                  WritePixel (rp, x, y);
                  h1prev = h1c;
                  h2prev = h2c;
               }
               if (h1!=h1prev || h2!=h2prev)  {
                  x = h1 + CENTERX;    /* draw to current point */
                  y = h2 + CENTERY;
                  if (ABS(h1-h1prev) > WWIDTH/2)
                     WritePixel (rp, x, y);
                  else
                     Draw (rp, x, y);
                  Move (rp, x, y);
               }
               h1prev = h1;
               h2prev = h2;
            }
            else  {                    /* else out of view */
               if (prev_in_view)  {    /* previous point was in view */
                  box_rim_point (lat, lam, latprev, lamprev, lat1, lam1,
                                 lat2, lam2, add360, prev_add360,
                                 &latc, &lamc);
                  h1c = (lamc-bcx) * xscale;
                  h2c = - (latc-bcy) * yscale;
                  x = h1c + CENTERX;   /* draw to rim point */
                  y = h2c + CENTERY;
                  if (ABS(h1c-h1prev) > WWIDTH/2)
                     WritePixel (rp, x, y);
                  else
                     Draw (rp, x, y);
                  Move (rp, x, y);
               }
            }
            prev_in_view = in_view;    /* save status of current point */
            prev_add360 = add360;
            latprev = lat;
            lamprev = lam;
         }
      }
   }
}

/* ================================================================ */

                                       /* find rim points in box view */

void  box_rim_point (double lat, double lam, double latprev,
                     double lamprev, double lat1, double lam1,
                     double lat2, double lam2, char add360,
                     char prev_add360, double *latc, double *lamc)
{
   double dlam, dlat, tlam1, tlam2, tlat1, tlat2;

   if (add360 != prev_add360)  {
      if (add360)
         lamprev += 360.;
      else
         lam += 360.;
   }
   dlam = lam - lamprev;               /* use parametric linear */
   dlat = lat - latprev;               /*   interpolation       */
   if (dlam == 0.)  {                  /* same longitude */
      tlam1 = tlam2 = -1.;
      tlat1 = (lat1 - latprev) / dlat;
      tlat2 = (lat2 - latprev) / dlat;
   }
   else if (dlat == 0.)  {             /* same latitude */
      tlam1 = (lam1 - lamprev) / dlam;
      tlam2 = (lam2 - lamprev) / dlam;
      tlat1 = tlat2 = -1.;
   }
   else  {
      tlam1 = (lam1 - lamprev) / dlam;
      tlam2 = (lam2 - lamprev) / dlam;
      tlat1 = (lat1 - latprev) / dlat;
      tlat2 = (lat2 - latprev) / dlat;
   }
   if (tlam1 >= 0. && tlam1 <= 1.)  {  /* now find rim point */
      (*lamc) = lam1;
      (*latc) = latprev + tlam1 * dlat;
   }
   else if (tlam2 >= 0. && tlam2 <= 1.)  {
      (*lamc) = lam2;
      (*latc) = latprev + tlam2 * dlat;
   }
   else if (tlat1 >= 0. && tlat1 <= 1.)  {
      (*lamc) = lamprev + tlat1 * dlam;
      (*latc) = lat1;
   }
   else  {
      (*lamc) = lamprev + tlat2 * dlam;
      (*latc) = lat2;
   }
}

/* ============================================================= */

void disable_menus (long flags)        /* disables menus */

{
   ModifyIDCMP (w, flags);
   Forbid ();
   w->Flags |= RMBTRAP;
   Permit ();
}

/* ============================================================= */

int displayhelp (void)                 /* displays help information */

{
   struct IntuiMessage *msg;
   int gadgetnum;

   if (helpbuffer == NULL)
      return (NOT_OK);
   if ((hw = (struct Window *) OpenWindow (&newhelpw)) == NULL)
      return (NOT_OK);
   SetPointer (hw, arrow, ARROW_SIZE/4-2, 16, ARROW_X_OFFSET,
               ARROW_Y_OFFSET);
   DrawBorder (hw->RPort, &border1, 0, 0);
   while (1)  {
      WaitPort (hw->UserPort);
      if ((msg = (struct IntuiMessage * ) GetMsg (hw->UserPort)) == NULL)
         continue;
      gadgetnum = ((struct Gadget *) (msg->IAddress))->GadgetID;
      ReplyMsg (msg);
      if (gadgetnum < NUMGADGETS-1)  {
         SetPointer (hw, waiter, WAITER_SIZE/4-2, 16, WAITER_X_OFFSET,
                     WAITER_Y_OFFSET);
         ModifyIDCMP (hw, NULL);
         dohelpitem (gadgetnum);
         ModifyIDCMP (hw, GADGETUP);
         SetPointer (hw, arrow, ARROW_SIZE/4-2, 16, ARROW_X_OFFSET,
                     ARROW_Y_OFFSET);
      }
      if (gadgetnum == NUMGADGETS-1)
         break;
   }
   ClearPointer (hw);
   CloseWindow (hw);
   return (OK);
}

/* ============================================================= */

void dohelpitem (int num)

{
   static char defaulttext[] = "No help information to display";
   char *txt;
   short result;
   int disp;

   if ((disp = gadgetlist[num].disp) < 0)
      txt = defaulttext;
   else
      txt = &(helpbuffer[disp]);
   trs.Text = txt;
   trs.Window = hw;
   trs.Title = gadgetlist[num].text;
   result = TextRequest (&trs);
}

/* ============================================================= */

int do_text (long xin, long yin, long color) /* get user text input */

{
   struct IntuiMessage *msg1;
   static char title_setdown[] = "Press left button to position text,"
                                 " right button to cancel";
   long x, y, xold, yold;
   int pixlength;
   double t;
                                       /* get user text */
   get_user_input (TEXT_TYPE, xin, yin, &t, &xold, &yold);
   pixlength = TextLength (rp, user_text_input, strlen(user_text_input));
   SetWindowTitles (w, 0, title_setdown);
   ShowTitle (s, TRUE);
   title_toggle = TRUE;
   SetDrMd (rp, JAM2 | COMPLEMENT);    /* turn on complement mode */
   SetPointer (w, transparent, TRANSPARENT_SIZE/4-2, 16,
               TRANSPARENT_X_OFFSET, TRANSPARENT_Y_OFFSET);
   drawbox (xold, yold, xold+pixlength-1, yold-9);
   ModifyIDCMP (w, IDCMPFLAGS | MOUSEMOVE);
   while (1)  {
      WaitPort (w->UserPort);          /* wait for mouse button  */
      if ((msg1 = (struct IntuiMessage *) GetMsg (w->UserPort)) == NULL)
         continue;
      else  {
         x = msg1->MouseX;             /* get current mouse position */
         y = msg1->MouseY;             /*   and erase old box        */
         if (msg1->Code == SELECTDOWN)  {
            ReplyMsg (msg1);           /* done if select button pressed */
            drawbox (xold, yold, xold+pixlength-1, yold-9);
            break;
         }
         else if (msg1->Class == MOUSEMOVE)  {
                                       /* else draw box at current position */
            drawbox (xold, yold, xold+pixlength-1, yold-9);
            drawbox (x, y, x+pixlength-1, y-9);
            xold = x;
            yold = y;
         }
         else if (msg1->Code == MENUDOWN)  {
            ReplyMsg (msg1);
            ModifyIDCMP (w, IDCMPFLAGS);
            drawbox (xold, yold, xold+pixlength-1, yold-9);
            SetDrMd (rp, JAM2);
            SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
                        ARROW_X_OFFSET, ARROW_Y_OFFSET);
            return (ABORT);
         }
         ReplyMsg (msg1);
      }
   }
   Move (rp, x, y-2);                  /* move to current mouse position  */
   SetDrMd (rp, JAM1);                 /* (-2 to allow for font baseline) */
   SetAPen (rp, color);                /* draw text */
   Text (rp, user_text_input, strlen(user_text_input));
   SetDrMd (rp, JAM2);
   ModifyIDCMP (w, IDCMPFLAGS);        /* enable original event types */
   SetPointer (w, arrow, ARROW_SIZE/4-2, 16,
               ARROW_X_OFFSET, ARROW_Y_OFFSET);
   return (OK);
}

/* ============================================================= */

void drawbox (long x1, long y1, long x2, long y2) /* draws a box */

{
   box_border.LeftEdge = x1;
   box_border.TopEdge  = y1;
   box_borderpts[2] = box_borderpts[4] = x2-x1;
   box_borderpts[5] = box_borderpts[7] = y2-y1;
   DrawBorder (rp, (struct Border *) &box_border, 0, 0);
}

/* ============================================================= */

int draw_line (long color)             /* draws a line based on */
                                       /*   mouse positions     */
{
   struct IntuiMessage *msg;
   char selectbutton;
   long x, y;

   selectbutton = FALSE;
   SetAPen (rp, color);
   SetDrMd (rp, JAM2);
   ModifyIDCMP (w, IDCMPFLAGS | MOUSEMOVE); /* enable mouse move events */
   while (1)  {
      WaitPort (w->UserPort);
      if ((msg = (struct IntuiMessage *) GetMsg (w->UserPort)) == NULL)
         continue;
      else if (msg->Code == MENUDOWN)  { /* stop if press menu button */
         ReplyMsg (msg);
         ModifyIDCMP (w, IDCMPFLAGS);  /* disable mouse move events */
         return (ABORT);
      }
      else if (msg->Code == SELECTDOWN)  {
         x = msg->MouseX;              /* get current mouse position */
         y = msg->MouseY;
         WritePixel (rp, x, y);
         Move (rp, x, y);
         selectbutton = TRUE;
      }
      else if ((selectbutton) && msg->Class == MOUSEMOVE)  {
         x = msg->MouseX;              /* get current mouse position */
         y = msg->MouseY;
         Draw (rp, x, y);              /* draw to current position */
         Move (rp, x, y);
      }
      else if ((selectbutton) && msg->Code == SELECTUP)
         break;
      ReplyMsg (msg);
   }
   ReplyMsg (msg);
   ModifyIDCMP (w, IDCMPFLAGS);        /* enable original event types */
   return (OK);
}

/* ============================================================= */

void enable_menus (void)               /* enables menus */

{
   ModifyIDCMP (w, IDCMPFLAGS);
   Forbid ();
   w->Flags &= ~RMBTRAP;
   Permit ();
}

/* ============================================================= */

int floodfill (long flood_color)       /* flood fills an area based */
                                       /*   on mouse position       */
{
   struct IntuiMessage *msgf;
   long x, y;
   
   while (1)  {
      WaitPort (w->UserPort);          /* wait for message */
      if ((msgf = (struct IntuiMessage *) GetMsg (w->UserPort)) == NULL)
         continue;
      else if (msgf->Code==SELECTDOWN)
         break;
      else if (msgf->Code == MENUDOWN)  {
         ReplyMsg (msgf);
         return (ABORT);
      }
      else
         ReplyMsg (msgf);
   }
   x = msgf->MouseX;                   /*  get mouse coordinates  */
   y = msgf->MouseY;
   ReplyMsg (msgf);
   SetAPen (rp, flood_color);
   Flood (rp, 1, x, y);                /* flood fill the region */
   return (OK);
}

/* ============================================================= */

void fullmap (long type)               /* draws cylindrical, polyconic  */
                                       /*   and pseudo-cylindrical maps */
{
   char crossed_over;
   short h1, h2, h1old, h2old;
   int i, na, nc, np;
   long x, y;
   double tlat, tlam, clat, slat, clam, slam, ty, tx;
   double facp, l1, p1, t1, z, alpha, txold;
   struct Arc *seg;
   struct Pt *pt;

   SetRast (rp, BLUE);                 /* clear screen */
   if (type==HAMMER_AITOFF_TYPE || type==SIMPLE_POLYCONIC_TYPE ||
       type==AITOFF_TYPE        || type==MOLLWEIDE_TYPE        ||
       type==SANSON_TYPE)
      fullmap_outline (type);          /* outline special maps */
   for (nc=0; nc<NUM_MAPS; ++nc)  {    /* do each map type */
      if (map[nc].plot != TRUE)        /* if not requested, skip */
         continue;
      SetAPen (rp, map[nc].color);     /* initialize color */
      seg = map[nc].seg;
      pt = map[nc].pt;                 /* do each segment */
      for (na=0; na<map[nc].nsegs; ++na)  {
         crossed_over = FALSE;         /* assume haven't crossed screen */
         txold = 0.;
         np = 0;
         for (i=seg[na].first; i<=seg[na].last; ++i)  {
            if (pt[i].code < detail_level) /* filter by detail level */
               continue;
            tlat = pt[i].lat;          /* latitude */
            tlat /= 60.;
            tlam = pt[i].lam;          /* longitude */
            tlam /= 60.;
            clat = cos (tlat*RAD);
            slat = sin (tlat*RAD);
            clam = cos (tlam*RAD);
            slam = sin (tlam*RAD);
            if (type==MOLLWEIDE_TYPE)  {    /* get auxiliary angle for */
               mollweide_angle (&t1, tlat); /*   Mollweide projection  */
               clat = cos (t1*RAD);
               slat = sin (t1*RAD);
            }
            if (!centered)
               rotate_coords (&clat, &slat, &clam, &slam, &tlat, &tlam);
            if (type==FLAT_TYPE)  {
               ty = tlat * VFACTOR;
               tx = tlam * HFACTOR;
            }
            else if (type==MERCATOR_TYPE)  {
               ty = log (tan ((tlat/2.+45.)*RAD)) * M_VFACTOR;
               tx = tlam * HFACTOR;
            }
            else if (type==LAMBERT_TYPE)  {
               ty = slat * LC_VFACTOR;
               tx = tlam * HFACTOR;
            }
            else if (type==GALL_TYPE)  {
               ty = tan (tlat*(RAD/2.)) * GC_VFACTOR;
               tx = tlam * HFACTOR;
            }
            else if (type==HAMMER_AITOFF_TYPE)  {
               facp = sqrt (1.+clat*cos (tlam*(RAD/2.)));
               ty = (slat/facp) * HA_VFACTOR;
               tx = (clat*sin(tlam*(RAD/2.))/facp) * HA_HFACTOR;
            }
            else if (type==SIMPLE_POLYCONIC_TYPE)  {
               ty = tlat*RAD;
               l1 = tlam*RAD;
               p1 = l1*slat;
               if (ABS(p1) > 0.01)  {
                  t1 = sin (p1/2.);
                  ty += 2.*clat*t1*t1/slat;
                  tx = (clat/slat) * sin(p1);
               }
               else  {
                  ty += l1*l1*slat*clat/2.;
                  tx = l1*clat;
               }
               ty *= SP_VFACTOR;
               tx *= SP_HFACTOR;       /* check for crossing screen */
               if (ty>PI2*SP_VFACTOR || ty<-PI2*SP_VFACTOR)  {
                  if ((tx>0. && txold<0.) ||
                      (tx<0. && txold>0.))
                     crossed_over = TRUE;
               }
               txold = tx;
            }
            else if (type==AITOFF_TYPE)  {
               l1 = tlam * (RAD/2.);
               z = acos (clat * cos (l1));
               alpha = atan2 (clat*sin(l1), slat);
               ty = z*cos(alpha)*AW_VFACTOR;
               tx = z*sin(alpha)*AW_HFACTOR;
            }
            else if (type==SANSON_TYPE)  {
               ty = tlat * VFACTOR;
               tx = tlam * clat * HFACTOR;
               if ((tx>0. && txold<0.) ||
                   (tx<0. && txold>0.))
                  crossed_over = TRUE;
               txold = tx;
            }
            else if (type==MOLLWEIDE_TYPE)  {
               tx = tlam * clat * HFACTOR;
               ty = slat * MO_VFACTOR;
            }
            if (tx<0.)
               tx -= 0.5;
            else
               tx += 0.5;
            if (ty<0.)
               ty -= 0.5;
            else
               ty += 0.5;
            h2 = -ty;
            h1 = tx;
            x = h1 + CENTERX;
            y = h2 + CENTERY;
            if (np!=0)  {              /* disallow identical adjacent pts */
               if (h1==h1old  && h2==h2old)
                  continue;
               else if (ABS(h1-h1old) > WWIDTH/5 || /* disallow spikes */
                        ABS(h2-h2old) > WHEIGHT/5)
                  WritePixel (rp, x, y);
               else if (crossed_over)  {  /* disallow crossing screen */
                  WritePixel (rp, x, y);
                  crossed_over = FALSE;
               }
               else
                  Draw (rp, x, y);
            }
            else
               WritePixel (rp, x, y);
            Move (rp, x, y);
            h1old = h1;
            h2old = h2;
            ++np;
         }
      }
   }
   if (got_flat_map != OK && type == FLAT_TYPE)  {
      savemappic ();                   /* save map in save area */
      got_flat_map = OK;
   }
}

/* ================================================================ */

void fullmap_outline (long type)       /* draws outlines of polyconic */
                                       /*   and pseudo-cylindrical    */
                                       /*   projections               */
{
   static double dlat = 5.;
   double lam, lat, l1, l2, cl2, sl2, c1, s1, t, t1, p1;
   double tx, ty, z, alpha;
   long x, y, oldpen;

   oldpen = rp->FgPen;
   SetAPen (rp, BLACK);
   for (lam=-180.; lam<=+180.; lam+=360.)  { /* longitude */
      l1 = lam*RAD;
      l2 = l1/2.;
      cl2 = cos(l2);
      sl2 = sin(l2);
      for (lat=90.; lat>=-90.; lat-=dlat)  {
         if (type==HAMMER_AITOFF_TYPE)  {
            t = lat*RAD;
            x = sl2 * cos(t) * HA_HFACTOR + CENTERX;
            y = -sin(t) * HA_VFACTOR + CENTERY;
         }
         else if (type==SIMPLE_POLYCONIC_TYPE)  {
            t = lat*RAD;
            c1 = cos(t);
            s1 = sin(t);
            p1 = l1*s1;
            if (ABS(p1) > 0.01)  {
               t1 = sin(p1/2.);
               tx = (c1/s1) * sin(p1);
               ty = t + 2.*c1*t1*t1/s1;
            }
            else  {
               tx = l1*c1;
               ty = t + l1*l1*s1*c1/2.;
            }
            x = tx * SP_HFACTOR + CENTERX;
            y = -ty * SP_VFACTOR + CENTERY;
         }
         else if (type==AITOFF_TYPE)  {
            t = lat*RAD;
            c1 = cos(t);
            s1 = sin(t);
            z = acos(c1*cl2);
            alpha = atan2 (c1*sl2, s1);
            tx = z*sin(alpha);
            ty = z*cos(alpha);
            x = tx * AW_HFACTOR + CENTERX;
            y = -ty * AW_VFACTOR + CENTERY;
         }
         else if (type==MOLLWEIDE_TYPE)  {
            mollweide_angle (&t, lat);
            x = lam * cos(t*RAD) * HFACTOR + CENTERX;
            y = -sin(t*RAD) * MO_VFACTOR + CENTERY;
         }
         else if (type==SANSON_TYPE)  {
            x = lam * cos(lat*RAD) * HFACTOR + CENTERX;
            y = -lat * VFACTOR + CENTERY;
         }
         if (x<0)
            x = 0;
         if (x>WWIDTH-1)
            x = WWIDTH-1;
         if (y<0)
            y = 0;
         if (y>WHEIGHT-1)
            y = WHEIGHT-1;
         if (lat!=90.)
            Draw (rp, x, y);
         Move (rp, x, y);
      }
   }
   SetAPen (rp, oldpen);
}

/* ============================================================= */

int getbox (long *x0, long *y0, long *x, long *y)
                                       /* selects a region to draw to */
                                       /*   larger scale              */
{
   struct IntuiMessage *msgf;
   char selectbutton;
   long xd, yd, x1, x2, y1, y2;

   selectbutton = FALSE;
   SetDrMd (rp, JAM2 | COMPLEMENT);    /* turn on complement mode and */
   ModifyIDCMP (w, IDCMPFLAGS | MOUSEMOVE); /* enable mouse move events */
   while (1)  {
      WaitPort (w->UserPort);
      if ((msgf = (struct IntuiMessage *) GetMsg (w->UserPort)) == NULL)
         continue;
      else if (msgf->Code == MENUDOWN)  { /* abort if user pressed */
         ReplyMsg (msgf);                 /*   menu button         */
         ModifyIDCMP (w, IDCMPFLAGS);
         SetDrMd (rp, JAM2);
         return (ABORT);
      }
      else if (msgf->Code==SELECTDOWN)  { /* if user pressed left button, */
         xd = x1 = msgf->MouseX;          /*   get initial mouse position */
         yd = y1 = msgf->MouseY;
         drawbox (x1, y1, xd, yd);     /* draw initial box */
         selectbutton = TRUE;
      }
      else if ((selectbutton) && msgf->Class==MOUSEMOVE)  {
         x2 = msgf->MouseX;
         y2 = msgf->MouseY;
         drawbox (x1, y1, xd, yd);     /* erase old box */
         xd = x2;
         yd = y2;
         drawbox (x1, y1, xd, yd);     /* draw new box */
      }
      else if ((selectbutton) && msgf->Code==SELECTUP)
         break;
      ReplyMsg (msgf);
   }
   ModifyIDCMP (w, IDCMPFLAGS);        /* disable mouse events and */
   x2 = msgf->MouseX;                  /*    erase current box     */
   y2 = msgf->MouseY;
   ReplyMsg (msgf);
   drawbox (x1, y1, xd, yd);
   SetDrMd (rp, JAM2);                 /* restore original drawing mode */
   *x0 = x1;
   *y0 = y1;
   *x = x2;
   *y = y2;
   if (x1==x2 || y1==y2)               /* error if box is of zero area */
      return (NOT_OK);
   if (x1>x2 && y1>y2)  {              /* ensure that the vertices of */
      *x0 = x2;                        /*   the box are in the proper */
      *y0 = y2;                        /*   order                     */
      *x = x1;
      *y = y1;
   }
   else if (x1<x2 && y1>y2)  {
      *x0 = x1;
      *y0 = y2;
      *x = x2;
      *y = y1;
   }
   else if (x1>x2 && y1<y2)  {
      *x0 = x2;
      *y0 = y1;
      *x = x1;
      *y = y2;
   }
   return (OK);
}

/* ============================================================= */

void getcoord (long x, long y, long type, double *lat, double *lam)
                                       /* converts screen coordinates   */
                                       /*   into latitude and longitude */
                                       /*   for flat and Mercator maps  */
{
   (*lam) = (x - CENTERX) / HFACTOR;
   if (type==FLAT_TYPE)
      (*lat) = (CENTERY - y) / VFACTOR;
   else
      (*lat) = -90. + 2.*atan(exp((CENTERY-y)/M_VFACTOR))/RAD;
}

/* ============================================================= */

                                       /* get latitude and longitude */
                                       /*   for Box Zoom In option   */
void getcoord_box (long x, long y, double latin[2], double lamin[2],
                   double *latout, double *lamout)
{
   (*lamout) = (((double) (x)) / WWIDTH)  * (lamin[1]-lamin[0]) + lamin[0];
   (*latout) = (((double) (y)) / WHEIGHT) * (latin[1]-latin[0]) + latin[0];
}

/* ============================================================= */

int get_min_max (void)                 /* find limits of each segment */

{
   FILE *fin;
   struct Arc *seg, *tseg;
   struct Pt *pt;
   int na, i, j, ix, first_seg;

   if ((seg = (struct Arc *) AllocRemember (&rememberkey,
                             NSEGS*sizeof(struct Arc),
                             MEMF_PUBLIC | MEMF_CLEAR)) == NULL)  {
      printf ("Unable to get space for limits array\n");
      return (NOT_OK);
   }
   for (i=0; i<NUM_MAPS; ++i)  {       /* link segment array to maps */
      first_seg = map[i].first_seg;
      map[i].seg = (struct Arc *) &(seg[first_seg].lat_min);
   }
   if ((fin = fopen (limitsfile, "rb")) != NULL)  {
      ix = fread (seg, sizeof(struct Arc), NSEGS, fin);
      fclose (fin);
      if (ix == NSEGS)
         return (OK);
      else
         printf ("Limits file corrupted, building it afresh\n");
   }
   for (i=0; i<NUM_MAPS; ++i)  {       /* build limits array in units */
      pt = map[i].pt;                  /* address of map points */
      tseg = map[i].seg;               /* address of segments for map */
      for (j=0, na=-1; j<map[i].numpts; ++j)  {
         if (pt[j].code > MAX_DETAIL_LEVEL)  {
            ++na;
            if (na >= map[i].nsegs)
               break;
            tseg[na].first = tseg[na].last = j;
            tseg[na].lat_min = tseg[na].lat_max = pt[j].lat;
            tseg[na].lam_min = tseg[na].lam_max = pt[j].lam;
            continue;
         }
         if (pt[j].lat < tseg[na].lat_min)
            tseg[na].lat_min = pt[j].lat;
         if (pt[j].lat > tseg[na].lat_max)
            tseg[na].lat_max = pt[j].lat;
         if (pt[j].lam < tseg[na].lam_min)
            tseg[na].lam_min = pt[j].lam;
         if (pt[j].lam > tseg[na].lam_max)
            tseg[na].lam_max = pt[j].lam;
         tseg[na].last = j;
      }
   }
   ix = 0;                             /* save limits to disk */
   if ((fin = fopen (limitsfile, "wb")) != NULL)  {
      ix = fwrite (seg, sizeof(struct Arc), NSEGS, fin);
      fclose (fin);
   }
   if (ix != NSEGS)                    /* error in saving to disk */
      printf ("Unable to save limits to disk, continuing anyway\n");
   return (OK);
}

/* ============================================================= */

void get_user_input (long type, long xin, long yin, double *d,
                     long *xout, long *yout)
                                       /* prompts for user input */
                                       /*   (text or doubleword) */
{
   struct IntuiMessage *msg;
   struct Gadget *g;
   long x, y;
                                       /* position the requester */
   x = xin - GAD_LEFT - 8*(NUM_CHAR-2);
   y = yin - GAD_TOP - 2;
   if ((x+TWIDTH) >= WWIDTH)
      x = WWIDTH - TWIDTH - 15;
   if (x < 10)
      x = 10;
   if ((y+THEIGHT) >= WHEIGHT)
      y = WHEIGHT - THEIGHT - 15;
   if (y < 5)
      y = 5;
   req.LeftEdge = x;
   req.TopEdge = y;
   if (type == ORBITAL_TYPE)  {        /* initialize for doubleword input */
      req.ReqText = &dtext;
      gadgetinput.Buffer = &user_double_input[0];
   }
   else  {                             /* initialize for text input */
      req.ReqText = &rtext;
      gadgetinput.Buffer = &user_text_input[0];
   }
   Request (&req, w);                  /* issue the requester */
   ModifyIDCMP (w, GADGETUP);          /* disable other events */
   ActivateGadget (&gad, w, &req);
   while (1)  {
      WaitPort (w->UserPort);
      if ((msg = (struct IntuiMessage *) GetMsg (w->UserPort)) == NULL)
         continue;
      else if (msg->Class == GADGETUP)  {
         g = (struct Gadget *) (msg->IAddress);
         if (g->GadgetID != GAD_FIRST)  {
            ReplyMsg (msg);
            continue;
         }
         *xout = msg->MouseX;          /* get mouse coordinates */
         *yout = msg->MouseY;
         ReplyMsg (msg);
         if (type == ORBITAL_TYPE)  {  /* get user value */
            *d = atof (&user_double_input[0]);
            if (errno == ERANGE)  {    /* trap erroneous input */
               *d = VIEW_HEIGHT;       /* store default value */
               sprintf (user_double_input, "%.2lf", *d);
               DisplayBeep (0);
            }
            else if ((*d) < MIN_HEIGHT)  {
               *d = MIN_HEIGHT;        /* store minimum value */
               sprintf (user_double_input, "%.2lf", *d);
               DisplayBeep (0);
            }
         }
         break;
      }
      else                             /* ignore inappropriate events */
         ReplyMsg (msg);
   }
   ModifyIDCMP (w, IDCMPFLAGS);
}

/* ============================================================= */

void globe (double lat0, double lam0, long type)
                                       /* draws globe projections */
{
   char first, prev_in_view, in_view, latzero;
   short h1, h1c, h1prev;              /* x-dist. (pix) from center */
   short h2, h2c, h2prev;              /* y-dist. (pix) from center */
   int i, na, nc;
   long x, y;
   double lam, lamc, lamprev;          /* longitude */
   double lat, latc, latprev;          /* latitude  */
   double c0, s0, c1, s1, c2, zp, zpprev;
   double hp, fac2, fac3, scale;
   double h1d, h2d, lat0p;
   double latmin, latmax, lammin, lammax;
   struct Arc *seg;
   struct Pt *pt;

   latzero = FALSE;
   if (type==GLOBE_TYPE)  {            /* ordinary globe view */
      hp = 0.;
      scale = 1.;
      fac3 = 1.;
   }
   else  {                             /* orbital globe view */
      hp = etap;
      scale = sqrt (1.-etap*etap);
      fac3 = (facp/(facp-hp)) * scale;
   }
   if (lat0==0.)  {
      c0 = 1.;
      s0 = 0.;
      if (type==GLOBE_TYPE)
         latzero = TRUE;               /* equatorial, ordinary globe view */
   }
   else  {
      lat0p = lat0 * RAD;
      c0 = cos (lat0p);
      s0 = sin (lat0p);
   }
   SetRast (rp, BLACK);                /* black background */
   SetAPen (rp, BLUE);                 /* blue globe */
   DrawEllipse (rp, (long) CENTERX, (long) CENTERY, (long) HRADIUS,
                (long) VRADIUS);
   Flood (rp, 1, CENTERX, CENTERY);
   for (nc=0; nc<NUM_MAPS; ++nc)  {    /* do each map type */
      if (map[nc].plot != TRUE)        /* if not requested, skip */
         continue;
      SetAPen (rp, map[nc].color);
      seg = map[nc].seg;
      pt = map[nc].pt;                 /* do each segment */
      for (na=0; na<map[nc].nsegs; ++na)  {
         latmin = ((double) (seg[na].lat_min)) / 60.;
         latmax = ((double) (seg[na].lat_max)) / 60.;
         lammin = ((double) (seg[na].lam_min)) / 60.;
         lammax = ((double) (seg[na].lam_max)) / 60.;
         if ((i = globe_in_view (latmax, lammin, latmin, lammax, lat0, lam0,
                                 hp)) != OK)
            continue;
         first = TRUE;
         for (i=seg[na].first; i<=seg[na].last; ++i)  {
            if (pt[i].code < detail_level) /* filter by detail level */
               continue;
            lat = pt[i].lat;           /* latitude */
            lat *= (RAD/60.);
            lam = pt[i].lam;           /* longitude */
            lam = (lam/60. - lam0) * RAD;
            if (lam<-PI)
               lam += TWOPI;
            if (lam>PI)
               lam -= TWOPI;
            c1 = cos(lat);             /* cosine of latitude */
            s1 = sin(lat);             /* sine of latitude */
            in_view = FALSE;           /* get status of current point */
            if (latzero)  {            /* equatorial globe view */
               zp = c1*cos (lam);
               if (lam>=-PI2 && lam<=+PI2)  {
                  in_view = TRUE;
                  h1 = HRADIUS * c1 * sin (lam);
                  h2 = -VRADIUS * s1;
               }
            }
            else  {                    /* oblique earth view */
               c2 = cos (lam);
               zp = s1*s0 + c1*c0*c2;
               if (zp>=hp)  {          /* zp > hp => in view */
                  in_view = TRUE;
                  h1d = HRADIUS * c1 * sin (lam);
                  h2d = -VRADIUS * (s1*c0 -c1*s0*c2);
                  if (type!=GLOBE_TYPE)  {
                     fac2 = (facp/(facp-zp)) * scale;
                     h1d *= fac2;
                     h2d *= fac2;
                  }
                  h1 = h1d;
                  h2 = h2d;
               }
            }
            if (first)  {              /* get status of first point */
               first = FALSE;
               if (in_view)  {         /* if first point is in view, */
                  x = h1 + CENTERX;    /*   move pen to first point  */
                  y = h2 + CENTERY;    /*   and plot it              */
                  Move (rp, x, y);
                  WritePixel (rp, x, y);
                  h1prev = h1;
                  h2prev = h2;
               }
               prev_in_view = in_view; /* save status of first point */
               latprev = lat;
               lamprev = lam;
               zpprev = zp;
               continue;
            }
            if (in_view)  {            /* current point is in view, */
               if (!prev_in_view)  {   /*   previous point was not  */
                  globe_rim_point (lat, lam, latprev, lamprev, zp, zpprev,
                                   c0, s0, fac3, type, latzero,
                                   &latc, &lamc, &h1c, &h2c);
                  x = h1c + CENTERX;   /* move to rim point & plot it */
                  y = h2c + CENTERY;
                  Move (rp, x, y);
                  WritePixel (rp, x, y);
                  h1prev = h1c;
                  h2prev = h2c;
               }
               if (h1!=h1prev || h2!=h2prev)  {
                  x = h1 + CENTERX;    /* draw to current point */
                  y = h2 + CENTERY;
                  Draw (rp, x, y);
                  Move (rp, x, y);
               }
               h1prev = h1;
               h2prev = h2;
            }
            else  {                    /* current point out of view, */
               if (prev_in_view)  {    /*   previous point in view   */
                  globe_rim_point (lat, lam, latprev, lamprev, zp, zpprev,
                                   c0, s0, fac3, type, latzero,
                                   &latc, &lamc, &h1c, &h2c);
                  x = h1c + CENTERX;   /* draw to rim point */
                  y = h2c + CENTERY;
                  Draw (rp, x, y);
                  Move (rp, x, y);
               }
            }
            prev_in_view = in_view;    /* save status of current point */
            latprev = lat;
            lamprev = lam;
            zpprev = zp;
         }
      }
   }
}

/* ============================================================= */

int globe_in_view (double lat0, double lam0, double lat1, double lam1,
                   double latv, double lamv, double hp)

                                       /* checks if a segment is visible   */
                                       /*   in a globe view                */
                                       /* lat0, lam0 = coords of upper     */
                                       /*   left corner of segment (deg)   */
                                       /* lat1, lam1 = coords of lower     */
                                       /*   right corner of segment (deg)  */
                                       /* latv, lamv = coords of viewpoint */
                                       /*   (deg)                          */
                                       /* hp = modified view height        */
{
   int k;
   double lat[2], lam[2], latt, lamt, del1, del2, eps;
   double lat0p, lam0p, lat1p, lam1p, latvp, lamvp, lamtp;

   lat0p = lat0 * RAD;                 /* convert to radians */
   lat1p = lat1 * RAD;
   lam0p = lam0 * RAD;
   lam1p = lam1 * RAD;
   latvp = latv * RAD;
   lamvp = lamv * RAD;
   del1 = (lat0p-lat1p)/10.;           /* divide into horizontal strips */
   if (del1 > 0.)  {
      eps = del1/10.;
      for (latt=lat0p; latt>=lat1p-eps; latt-=del1)  {
         if ((k = limit_lam (lam, latt, latvp, hp)) == OK)  {
            lam[0] += lamv;
            lam[1] += lamv;
            if (lam[0] < -PI)
               lam[0] += TWOPI;
            if (lam[0] > +PI)
               lam[0] -= TWOPI;
            if (lam[1] < -PI)
               lam[1] += TWOPI;
            if (lam[1] > +PI)
               lam[1] -= TWOPI;
            if (lam[0] <= lam1 && lam[1] >= lam0)
               return (OK);
         }
      }
   }
   del2 = (lam1p-lam0p)/10.;           /* divide into vertical strips */
   if (del2 > 0.)  {
      eps = del2/10.;
      for (lamt=lam0p-lamvp; lamt<=lam1p-lamvp+eps; lamt+=del2)  {
         if ((lamtp = lamt) < -PI)
            lamtp += TWOPI;
         if (lamtp > +PI)
            lamtp -= TWOPI;
         if ((k = limit_lat (lat, latvp, lamtp, hp)) == OK)  {
            if (lat[0] >= lat1 && lat[1] <= lat0)
               return (OK);
         }
      }
   }
   if (del1 > 0. || del2 > 0.)
      return (NOT_OK);
   return (OK);                        /* let globe() do degenerate case */
}

/* ============================================================= */

                                       /* get rim points for globe */

void globe_rim_point (double lat, double lam, double latprev,
                      double lamprev, double zp, double zpprev,
                      double c0, double s0, double fac3, long type,
                      char latzero, double *latc, double *lamc,
                      short *h1c, short *h2c)
{
   static int itmax = 20;
   double facz, dlat, dlam, c1, h1d, h2d;
   long x, y ,color;
   int i;
   short inc1, inc2;

   facz = zp / (zpprev-zp);            /* get rim point by linear */
   (*latc) = lat - (latprev-lat)*facz; /*   interpolation         */
   (*lamc) = lam - (lamprev-lam)*facz;
   dlat = fabs (lat-latprev);
   dlam = fabs (lam-lamprev);
   if ( fabs ((*latc)-latprev)> dlat || /* if rim point not between */
        fabs ((*latc)-lat)    > dlat)   /*   current and previous   */
      (*latc) = (lat+latprev)/2.;       /*   point, use midpoint    */
   if ( fabs ((*lamc)-lamprev)> dlam ||
        fabs ((*lamc)-lam)    > dlam )
      (*lamc) = (lam+lamprev)/2.;
   if (latzero)  {
      (*h1c) = HRADIUS * cos ((*latc)) * sin ((*lamc));
      (*h2c) = -VRADIUS * sin ((*latc));
   }
   else  {
      c1 = cos ((*latc));
      h1d = HRADIUS * c1 * sin ((*lamc));
      h2d = -VRADIUS * (sin ((*latc))*c0 - c1*s0*cos ((*lamc)));
      if (type!=GLOBE_TYPE)  {
         h1d *= fac3;
         h2d *= fac3;
      }
      (*h1c) = h1d;
      (*h2c) = h2d;
   }                                   /* find nearest actual rim point */
   if ((*h1c)>=0)                      /* right half */
      inc1 = +1;
   else                                /* left half */
      inc1 = -1;
   if ((*h2c)>=0)                      /* bottom half */
      inc2 = +1;
   else                                /* top half */
      inc2 = -1;
   x = (*h1c) + CENTERX + 5*inc1;      /* coordinates of test pixel */
   y = (*h2c) + CENTERY + 5*inc2;
   for (i=0; i<itmax; ++i)  {          /* look for nearest black pixel */
      if ((color = ReadPixel (rp, x, y))==BLACK)
         break;
      x += inc1;
      y += inc2;
   }
   x -= inc1;
   y -= inc2;
   for (i=0; i<itmax; ++i)  {          /* look back for non-black one */
      if ((color = ReadPixel (rp, x, y)) != BLACK)
         break;
      x -= inc1;
      y -= inc2;
   }
   (*h1c) = x - CENTERX;
   (*h2c) = y - CENTERY;
}

/* ============================================================= */

void grid (long type, double lat0, double lam0)
                                       /* controls drawing of grids */
{
   static double lat_interval = 20.;
   static double lam_interval = 30.;
   static double del = 3.;
   char crossed_over;
   long oldpen, x, y, xold, yold;
   double lam, lat, tlam, tlat, tlat0, tx, ty;
   double l1, facp, p1, t1, z, alpha, txold;
   double clam, slam, clat, slat;
   double clam0, slam0, clat0, slat0;

   oldpen = rp->FgPen;                 /* save original pen color */
   if (type==GLOBE_TYPE || type==ORBITAL_TYPE || type==ZOOM_IN_TYPE ||
       type==ZOOM_OUT_TYPE)
      grid_globe (type, lat0, lam0);   /* draw grid for globe */
   else  {                             /* otherwise grid for */
                                       /*   plane maps       */
      SetAPen (rp, BLACK);             /* set grid color to black */
      for (lam=-180.; lam<=+180.; lam+=lam_interval)  { /* longitude lines */
         crossed_over = FALSE;
         txold = 0.;
         if (lam==0. || lam==180.)
            SetAPen (rp, WHITE);
         clam0 = cos (lam*RAD);
         slam0 = sin (lam*RAD);
         for (lat=90.; lat>=-90.; lat-=del)  {
            tlam = lam;
            tlat = lat;
            if (type==MOLLWEIDE_TYPE)
               mollweide_angle (&tlat, lat);
            clat = cos (tlat*RAD);
            slat = sin (tlat*RAD);
            clam = clam0;
            slam = slam0;
            if (!centered)
               rotate_coords (&clat, &slat, &clam, &slam, &tlat, &tlam);
            tx = tlam * HFACTOR;
            l1 = tlam * RAD;
            if (type==FLAT_TYPE)
               ty = tlat * VFACTOR;
            else if (type==MERCATOR_TYPE)
               ty = log (tan ((tlat/2.+45.)*RAD)) * M_VFACTOR;
            else if (type==LAMBERT_TYPE)
               ty = slat * LC_VFACTOR;
            else if (type==GALL_TYPE)
               ty = tan (tlat*(RAD/2.)) * GC_VFACTOR;
            else if (type==MOLLWEIDE_TYPE)  {
               tx *= clat;
               ty = slat * MO_VFACTOR;
            }
            else if (type==SANSON_TYPE)  {
               tx *= clat;
               ty = tlat * VFACTOR;
            }
            else if (type==HAMMER_AITOFF_TYPE)  {
               facp = sqrt (1.+clat*cos(l1/2.));
               tx = (clat*sin(l1/2.)/facp) * HA_HFACTOR;
               ty = (slat/facp) * HA_VFACTOR;
            }
            else if (type==SIMPLE_POLYCONIC_TYPE)  {
               p1 = l1*slat;
               if (ABS (p1) > 0.01)  {
                  t1 = sin (p1/2.);
                  tx = (clat/slat) * sin(p1);
                  ty = (tlat*RAD) + 2.*clat*t1*t1/slat;
               }
               else  {
                  tx = l1 * clat;
                  ty = (tlat*RAD) + l1*l1*slat*clat/2.;
               }
               tx *= SP_HFACTOR;
               ty *= SP_VFACTOR;       /* check for crossing screen */
               if (ty>PI2*SP_VFACTOR || ty<-PI2*SP_VFACTOR)  {
                  if ((tx>0. && txold<0.) ||
                      (tx<0. && txold>0.))
                     crossed_over = TRUE;
               }
               txold = tx;
            }
            else if (type==AITOFF_TYPE)  {
               if (tlam==0.)  {
                  z = tlat*RAD;
                  alpha = 0.;
               }
               else  {
                  z = acos (clat*cos(l1/2.));
                  alpha = atan2 (clat*sin(l1/2.), slat);
               }
               tx = z*sin(alpha) * AW_HFACTOR;
               ty = z*cos(alpha) * AW_VFACTOR;
            }
            if (tx<0.)
               tx -= 0.5;
            else
               tx += 0.5;
            if (ty<0.)
               ty -= 0.5;
            else
               ty += 0.5;
            x = tx + CENTERX;
            y = -ty + CENTERY;
            if (x<0)
               x = 0;
            if (x>WWIDTH-1)
               x = WWIDTH-1;
            if (y<0)
               y = 0;
            if (y>WHEIGHT-1)
               y = WHEIGHT-1;
            if (lat!=90.)  {
               if (ABS (x-xold) > WWIDTH/5 ||
                   ABS (y-yold) > WHEIGHT/5)
                  WritePixel (rp, x, y);
               else if (crossed_over)  { /* disallow crossing screen */
                  WritePixel (rp, x, y);
                  crossed_over = FALSE;
               }
               else
                  Draw (rp, x, y);
            }
            Move (rp, x, y);
            xold = x;
            yold = y;
         }
         if (lam==0. || lam==180.)
            SetAPen (rp, BLACK);
      }
      for (lat=80.; lat>=-80.; lat-=lat_interval)  { /* latitude lines */
         crossed_over = FALSE;
         txold = 0.;
         tlat0 = lat;
         if (type==MOLLWEIDE_TYPE)
            mollweide_angle (&tlat0, lat);
         clat0 = cos (tlat0*RAD);
         slat0 = sin (tlat0*RAD);
         if (lat==0.)
            SetAPen (rp, WHITE);
         for (lam=-180.; lam<=+180.; lam+=del)  {
            tlat = tlat0;
            tlam = lam;
            clam = cos (lam*RAD);
            slam = sin (lam*RAD);
            clat = clat0;
            slat = slat0;
            if (!centered)
               rotate_coords (&clat, &slat, &clam, &slam, &tlat, &tlam);
            tx = tlam * HFACTOR;
            l1 = tlam * RAD;
            if (type==FLAT_TYPE)
               ty = tlat * VFACTOR;
            else if (type==MERCATOR_TYPE)
               ty = log (tan ((tlat/2.+45.)*RAD)) * M_VFACTOR;
            else if (type==LAMBERT_TYPE)
               ty = slat * LC_VFACTOR;
            else if (type==GALL_TYPE)
               ty = tan (tlat*(RAD/2.)) * GC_VFACTOR;
            else if (type==MOLLWEIDE_TYPE)  {
               tx *= clat;
               ty = slat * MO_VFACTOR;
            }
            else if (type==SANSON_TYPE)  {
               tx *= clat;
               ty = tlat * VFACTOR;
            }
            else if (type==HAMMER_AITOFF_TYPE)  {
               facp = sqrt (1.+clat*cos(l1/2.));
               tx = ((clat*sin(l1/2.))/facp) * HA_HFACTOR;
               ty = (slat/facp) * HA_VFACTOR;
            }
            else if (type==SIMPLE_POLYCONIC_TYPE)  {
               t1 = sin (l1*slat/2.);
               if (tlat==0.)  {
                  tx = l1 * SP_HFACTOR;
                  ty = 0.;
               }
               else  {
                  tx = (clat*sin(l1*slat)/slat) * SP_HFACTOR;
                  ty = (tlat*RAD + 2.*clat*t1*t1/slat) * SP_VFACTOR;
               }                       /* check for changing lobes */
               if (ty>PI2*SP_VFACTOR || ty<-PI2*SP_VFACTOR)  {
                  if ((tx>0. && txold<0.) ||
                      (tx<0. && txold>0.))
                     crossed_over = TRUE;
               }
               txold = tx;
            }
            else if (type==AITOFF_TYPE)  {
               z = acos (clat*cos(l1/2.));
               alpha = atan2 (clat*sin(l1/2.), slat);
               tx = z*sin(alpha) * AW_HFACTOR;
               ty = z*cos(alpha) * AW_VFACTOR;
            }
            if (tx<0.)
               tx -= 0.5;
            else
               tx += 0.5;
            if (ty<0.)
               ty -= 0.5;
            else
               ty += 0.5;
            x = tx + CENTERX;
            y = -ty + CENTERY;
            if (x<0)
               x = 0;
            if (x>WWIDTH-1)
               x = WWIDTH-1;
            if (y<0)
               y = 0;
            if (y>WHEIGHT-1)
               y = WHEIGHT-1;
            if (lam!=-180.)  {
               if (ABS (x-xold) > WWIDTH/5 ||
                   ABS (y-yold) > WHEIGHT/5)
                  WritePixel (rp, x, y);
               else if (crossed_over)  { /* disallow crossing screen */
                  WritePixel (rp, x, y);
                  crossed_over = FALSE;
               }
               else
                  Draw (rp, x, y);
            }
            Move (rp, x, y);
            xold = x;
            yold = y;
         }
         if (lat==0.)
            SetAPen (rp, BLACK);
      }
   }
   SetAPen (rp, oldpen);               /* restore pen color */
}

/* ============================================================= */

void grid_globe (long type, double lat0, double lam0)
                                       /* controls drawing globe grid */
{
   static double lat_interval = 20.;
   static double lam_interval = 30.;
   static double delta = 3.;
   char first;
   int k;
   double lat, lam, c0, s0, c1, s1, c2, s2, rlam, rlat;
   double hp, scale, fac3, dlat, dlam, ddelt;
   double lamp[2], latp[2];
   
   lat0 *= RAD;
   c0 = cos (lat0);
   s0 = sin (lat0);
   dlat = lat_interval;
   dlam = lam_interval;
   ddelt = delta;
   if (type==GLOBE_TYPE)  {            /* ordinary globe view */
      hp = 0.;
      scale = 1.;
      fac3 = 1.;
   }
   else  {                             /* orbital view */
      hp = etap;
      scale = sqrt (1.-etap*etap);
      fac3 = (facp/(facp-hp)) * scale;
      if (view_height<=1200.)  {
         dlat /= 4.;
         dlam /= 4.;
         ddelt /= 5.;
      }
   }
   SetAPen (rp, BLACK);                /* grid lines in black */
   for (lat=80.; lat>=-80.; lat-=dlat)  { /* lines of equal latitude */
      rlat = lat*RAD;
      if ((k=limit_lam (&lamp[0], rlat, lat0, hp))!=OK)
         continue;                     /* skip if entirely out of view */
      lamp[0] += lam0;
      lamp[1] += lam0;
      k = lamp[0]/ddelt - 1.;          /* express limits as multiple */
      lamp[0] = k*ddelt;               /*   of ddelt                 */
      k = lamp[1]/ddelt + 1.;
      lamp[1] = k*ddelt;
      c1 = cos (rlat);
      s1 = sin (rlat);
      first = TRUE;
      if (lat==0.)
         SetAPen (rp, WHITE);          /* draw equator in white */
      for (lam=lamp[0]; lam<=lamp[1]; lam+=ddelt)  {
         rlam = (lam-lam0)*RAD;
         if (rlam<-PI)
            rlam += TWOPI;
         if (rlam>+PI)
            rlam -= TWOPI;
         c2 = cos (rlam);
         s2 = sin (rlam);
         grid_globe_plot (rlat, rlam, c0, s0, c1, s1, c2, s2,
                          type, hp, scale, fac3, &first);
      }
      if (lat==0.)
         SetAPen (rp, BLACK);          /* reset pen to black */
   }
   for (lam=-180.; lam<+180.; lam+=dlam)  { /* meridian circles */
      rlam = (lam-lam0)*RAD;
      if (rlam<-PI)
         rlam += TWOPI;
      if (rlam>+PI)
         rlam -= TWOPI;
      if ((k=limit_lat (&latp[0], lat0, rlam, hp))!=OK)
         continue;                     /* skip if entirely out of view */
      k = latp[0]/ddelt + 1.;          /* express limits as multiple */
      latp[0] = k*ddelt;               /*   of ddelt                 */
      k = latp[1]/ddelt - 1.;
      latp[1] = k*ddelt;
      if (latp[0]>=90.)                /* exclude North polar point */
         latp[0] = 90. - ddelt;
      if (latp[1]<=-90.)               /* exclude South polar point */
         latp[1] = -90. + ddelt;
      c2 = cos (rlam);
      s2 = sin (rlam);
      first = TRUE;
      if (lam==0. || lam==-180.)       /* prime meridian in white */
         SetAPen (rp, WHITE);
      for (lat=latp[0]; lat>=latp[1]; lat-=ddelt)  {
         rlat = lat*RAD;
         c1 = cos (rlat);
         s1 = sin (rlat);
         grid_globe_plot (rlat, rlam, c0, s0, c1, s1, c2, s2,
                          type, hp, scale, fac3, &first);
      }
      if (lam==0. || lam==-180.)
         SetAPen (rp, BLACK);          /* reset pen to black */
   }
}

/* ============================================================= */

void grid_globe_plot (double lat, double lam, double c0, double s0,
                      double c1, double s1, double c2, double s2,
                      long type, double hp, double scale, double fac3,
                      char *first)
                                       /* draws globe grids */
{
   static char latzero = FALSE;
   static char prev_in_view;
   static short h1prev, h2prev;
   static double latprev, lamprev, zpprev;
   char in_view;
   short h1, h1c;                      /* x-dist. (pix) from center */
   short h2, h2c;                      /* y-dist. (pix) from center */
   long x, y;
   double lamc;                        /* longitude */
   double latc;                        /* latitude  */
   double zp, h1d, h2d, fac2;
   
   in_view = FALSE;                    /* get status of current point */
   zp = s1*s0 + c1*c0*c2;
   if (zp>=hp)  {                      /* zp > hp => in view */
      in_view = TRUE;
      h1d = HRADIUS * c1 * s2;
      h2d = -VRADIUS * (s1*c0 - c1*s0*c2);
      if (type!=GLOBE_TYPE)  {
         fac2 = (facp/(facp-zp))*scale;
         h1d *= fac2;
         h2d *= fac2;
      }
      h1 = h1d;
      h2 = h2d;
   }
   if ((*first))  {                    /* get status of first point */
      *first = FALSE;
      if (in_view)  {                  /* if first point is in view, */
         x = h1 + CENTERX;             /*   move pen to first point  */
         y = h2 + CENTERY;             /*   and plot it              */
         Move (rp, x, y);
         WritePixel (rp, x, y);
         h1prev = h1;
         h2prev = h2;
      }
      prev_in_view = in_view;          /* save status of first point */
      latprev = lat;
      lamprev = lam;
      zpprev = zp;
      return;
   }
   if (in_view)  {                     /* if current point is in view, */
      if (!prev_in_view)  {            /*   but previous point was not */
         globe_rim_point (lat, lam, latprev, lamprev, zp, zpprev,
                          c0, s0, fac3, type, latzero,
                          &latc, &lamc, &h1c, &h2c);
         x = h1c + CENTERX;            /* move to rim point & plot it */
         y = h2c + CENTERY;
         Move (rp, x, y);
         WritePixel (rp, x, y);
         h1prev = h1c;
         h2prev = h2c;
      }
      if (h1!=h1prev || h2!=h2prev)  {
         x = h1 + CENTERX;             /* draw to current point */
         y = h2 + CENTERY;
         Draw (rp, x, y);
         Move (rp, x, y);
      }
      h1prev = h1;
      h2prev = h2;
   }
   else  {                             /* current point is out of view   */
      if (prev_in_view)  {             /* if previous point was in view, */
         globe_rim_point (lat, lam, latprev, lamprev, zp, zpprev,
                          c0, s0, fac3, type, latzero,
                          &latc, &lamc, &h1c, &h2c);
         x = h1c + CENTERX;            /* draw to rim point */
         y = h2c + CENTERY;
         Draw (rp, x, y);
         Move (rp, x, y);
      }
   }
   prev_in_view = in_view;             /* save status of current point */
   latprev = lat;
   lamprev = lam;
   zpprev = zp;
}

/* ============================================================= */

int init_helpitems (void)              /* initializes help info */

{
   FILE *helpfile;
   struct Gadget *gadgetpointer;
   long x, y;
   int gadgetnum;
   int ix, numrd, i;

   if ((helpbuffer = (char *) calloc (1, (unsigned) HELPBUFSIZE)) == NULL)
      return (NOT_OK);
   if ((helpfile = fopen (helpfilename, "rb")) == NULL)  {
      free (helpbuffer);
      helpbuffer = NULL;
      return (NOT_OK);
   }
   if ((numrd = fread (helpbuffer, sizeof(char), HELPBUFSIZE, helpfile))
       <= 0)  {
      fclose (helpfile);
      free (helpbuffer);
      helpbuffer = NULL;
      return (NOT_OK);
   }
   fclose (helpfile);
   ix = 1;
   gadgetlist[0].disp = 0;
   for (i=0; i<numrd-1; ++i)  {        /* get disp to each help entry */
      if (helpbuffer[i] == '\f')  {    /* replace formfeed with eol to */
         helpbuffer[i] = '\0';         /*   facilitate text display    */
         gadgetlist[ix].disp = i+1;
         ++ix;
         if (ix > NUMGADGETS)
            break;
      }
   }                                   /* now link the gadgets */
   for (gadgetnum=0; gadgetnum<NUMGADGETS; ++gadgetnum)  {
      x = HGADGETSTARTX + (gadgetnum&1) * (HWIDTH/2);
      y = HGADGETSTARTY + s->Font->ta_YSize + (gadgetnum/2) *
          (HGAD_HEIGHT+HGAP);
      LinkGadget (&gadgetblocks[gadgetnum],
                  gadgetlist[gadgetnum].text,
                  &newhelpw, (long) x, (long) y);
      gadgetblocks[gadgetnum].Gadget.GadgetID = gadgetnum;
   }                                   /* put exit gadget at top */
   gadgetpointer = &gadgetblocks[NUMGADGETS-1].Gadget;
   gadgetpointer->TopEdge = HTOP_MARGIN;
   gadgetpointer->LeftEdge = (HWIDTH-gadgetpointer->Width) / 2;
   return (OK);
}

/* ============================================================= */

void init_requesters (void)            /* initializes the requesters */

{
   static char title_file[] = "Select save file name:";
   unsigned char *s, *t;   

   InitRequester (&req);               /* initialize the requester */
   req.LeftEdge  = TLEFT;
   req.TopEdge   = TTOP;
   req.Width     = TWIDTH;
   req.Height    = THEIGHT;
   req.ReqGadget = &gad;
   req.ReqText   = &rtext;             /* assume text input */
   req.BackFill  = ORANGE;
   req.Flags     = 0;
   req.ReqBorder = &border_top;
   s = &user_text_input[0];            /* copy default text string */
   t = &default_text[0];
   while ((*s++ = *t++) != '\0')
            ;
   s = &user_double_input[0];          /* copy default orbital height */
   sprintf (default_double, "%.2lf", VIEW_HEIGHT);
   t = &default_double[0];
   while ((*s++ = *t++) != '\0')
            ;

   colorstruct.window = w;             /* window id for color requester */

   pathname[0]              = 0;       /* initialize file requester */
   filereq.Title            = title_file;  /* window title          */
   filereq.Dir              = directoryname;  /* space for dir name */
   filereq.File             = filename;    /* space for file name   */
   filereq.PathName         = pathname;    /* space for path name   */
   filereq.Window           = w;           /* display on map window */
   filereq.Flags            = FRQCACHINGM; /* cache the filenames   */
   filereq.Flags           |= FRQSAVINGM;  /* saving files          */
   filereq.dirnamescolor    = ORANGE;      /* dir names             */
   filereq.filenamescolor   = LT_YEL;      /* file names            */
   filereq.devicenamescolor = BLACK;       /* device names          */
   filereq.detailcolor      = BLUE;        /* window gadgets        */
   filereq.blockcolor       = DK_YEL;      /* title bar             */
   filereq.gadgettextcolor  = DK_YEL;      /* text in gadgets       */
   filereq.textmessagecolor = DK_YEL;      /* text in title         */
   filereq.stringnamecolor  = DK_YEL;      /* prompts               */
   filereq.stringgadgetcolor= LT_BL;       /* borders               */
   filereq.boxbordercolor   = LT_BL;
   filereq.gadgetboxcolor   = LT_BL;

   trs.MiddleText = "OK";              /* initialize items in */
   trs.PositiveText = 0;               /*   help requester    */
   trs.NegativeText = 0;
   trs.KeyMask = 0;
   trs.textcolor = ORANGE;
   trs.detailcolor = BLUE;
   trs.blockcolor = DK_YEL;
   trs.versionnumber = REQVERSION;
   trs.Timeout = 0;
   trs.AbortMask = 0;
   trs.rfu1 = 0;
}

/* ============================================================= */

int limit_lam (double *lam, double lat, double lat0, double etap)
                                       /* computes limits on longitude */
                                       /*   for constant latitude      */
{
   double alpha;
   
   alpha = (etap-sin(lat)*sin(lat0)) / (cos(lat)*cos(lat0));
   if (alpha<=-1.)  {                  /* negative => lamda covers */
      lam[0] = -180.;                  /*   entire hemisphere      */
      lam[1] = +180.;
      return (OK);
   }
   else if (alpha>=+1.)                /* positive => nothing in view */
      return (NOT_OK);
   else  {                             /* otherwise, compute limits */
      lam[0] = -acos (alpha)/RAD;
      lam[1] = -lam[0];
      return (OK);
   }
}

/* ============================================================= */

int limit_lat (double *lat, double lat0, double lam, double etap)
                                       /* computes limits on latitude */
                                       /*   for constant longitude    */
{
   double radical, a, b, sum, fac1;
   
   a = sin (lat0);
   b = cos (lat0) * cos (lam);
   sum = a*a + b*b;
   if ((radical = sum - etap*etap) <= 0.) /* no real solutions */
      return (NOT_OK);
   else  {                             /* two real solutions       */
      radical = sqrt (radical);        /* solve quadratic equation */
      fac1 = (a*etap + b*radical)/sum;
      lat[0] = asin (fac1)/RAD;
      fac1 = (a*etap - b*radical)/sum;
      lat[1] = asin (fac1)/RAD;
      if (lat[0]<lat[1])  {            /* put in correct order */
         b = lat[0];
         lat[0] = lat[1];
         lat[1] = b;
      }
      if (a>etap)                      /* check North pole */
         lat[0] = 90.;
      if (a<-etap)                     /* check South pole */
         lat[1] = -90.;
      return (OK);
   }
}

/* ================================================================ */

void loadmappic (void)                 /* loads initial flat map */

{
   IFFP Iresult;

   IlbmFrame.iWindow = w;
   IlbmFrame.iScreen = s;
   IlbmFrame.iUserFlags = 0;
   got_flat_map = NOT_OK;
   if ((Iresult = LoadIFFToWindow (flatmapfile, &IlbmFrame)) == IFF_OKAY)  {
      got_flat_map = OK;
      savemappic ();
      ShowTitle (s, TRUE);
      title_toggle = TRUE;
   }
}

/* ================================================================ */

void mollweide_angle (double *psi, double lat)
                                       /* solves trancendental equation */
                                       /*   for Mollweide's map using   */
                                       /*   Newton-Raphson iteration    */
{
   static double eps = 0.01 * RAD;     /* result good to 0.01 degree */
   double pn, pnp1, c, twopn;

   if (lat==90. || lat==-90.)
      (*psi) = lat;
   else  {
      pn = lat * RAD;
      c = PI * sin (pn);
      while (1)  {
         twopn = 2.*pn;
         pnp1 = pn - 0.5 * (twopn+sin(twopn)-c) / (1.+cos(twopn));
         if (ABS(pnp1-pn) < eps)
            break;
         pn = pnp1;
      }
      (*psi) = pnp1 / RAD;
   }
}

/* ================================================================ */

int printmap (struct Window *win)      /* prints displayed map */

{
   struct IODRPReq *ioreq;
   struct MsgPort *printerport;
   struct IntuiMessage *msg;
   static char printerportname[] = "drmapdump";
   static char printerdevice[]   = "printer.device";
   static char print_canx[]      = "Printing canceled. Please wait ...";
   static char print_error[]     = "Printer error ... printing aborted";
   char abort;
   int returncode;
   unsigned long signal, winsig, printsig;

   abort = NULL;
   ModifyIDCMP (win, MOUSEBUTTONS);    /* disable all but mousebuttons */
   if ((printerport = CreatePort (printerportname, 0)) != NULL)  {
      if ((ioreq = (struct IODRPReq *) CreateExtIO (printerport,
                   sizeof (struct IODRPReq))) != NULL)  {
         if ( ! (OpenDevice (printerdevice, 0,
                             (struct IORequest *) ioreq, 0)))  {
            ioreq->io_Command = CMD_FLUSH;
            DoIO ((struct IORequest *) ioreq);
            if (ioreq->io_Error == PDERR_NOERR)  {
               winsig = 1 << win->UserPort->mp_SigBit;
               printsig = 1 << printerport->mp_SigBit;
               ioreq->io_Command = PRD_DUMPRPORT;
               ioreq->io_RastPort = rp;
               ioreq->io_ColorMap = vp->ColorMap;
               ioreq->io_Modes = VMODE; /* vp->Modes;*/
               ioreq->io_SrcX = 0;
               ioreq->io_SrcY = 0;
               ioreq->io_SrcWidth = WWIDTH;
               ioreq->io_SrcHeight = WHEIGHT;
               ioreq->io_DestCols = 0;
               ioreq->io_DestRows = 0;
               ioreq->io_Special = SPECIAL_ASPECT|SPECIAL_FULLROWS;

               SendIO ((struct IORequest *) ioreq);
               while (abort == NULL)  {
                  signal = Wait (printsig | winsig);
                  if (signal & winsig)  {
                     while ((msg = (struct IntuiMessage *)
                                   GetMsg (win->UserPort)) != NULL)  {
                        if (msg->Code == MENUDOWN)  {
                           returncode = ABORT;
                           SetWindowTitles (w, 0, print_canx);
                           abort |= U_ABORT;
                        }
                        ReplyMsg ((struct IntuiMessage *) msg);
                     }
                  }
                  if (signal & printsig)  {
                     if (ioreq->io_Error != PDERR_NOERR)  {
                        returncode = NOT_OK;
                        SetWindowTitles (w, 0, print_error);
                        abort |= P_ABORT;
                     }
                     else
                        abort |= NOABORT; /* ahem, not aborted, but ended OK */
                  }
               }
               if (abort == U_ABORT)  { /* WAIT A MOMENT, crashes if */
                                        /*   printer.device is being */
                                        /*   loaded and printing is  */
                                        /*   canceled                */
                  DisplayBeep (0);
                  Delay (8 * TICKS_PER_SECOND);
                  AbortIO ((struct IORequest *)ioreq);
                  WaitIO ((struct IORequest *)ioreq);
               }
               else if (abort & NOABORT)
                  returncode = OK;
               while ((struct MsgPort *) GetMsg (printerport))
                  ;
            }
            CloseDevice ((struct IORequest *) ioreq);
         }
         DeleteExtIO ((struct IORequest *) ioreq);
      }
      DeletePort (printerport);
   }
   ModifyIDCMP (win, IDCMPFLAGS);      /* restore normal events */
   return (returncode);
}

/* ============================================================= */

int readmap (void)                     /* reads map files into memory */

{
   FILE *fin;
   int i, num_read, num_to_read;
   struct Pt *pt;

   for (i=0; i<NUM_MAPS; ++i)  {
      num_to_read = (map[i].numpts) * sizeof (struct Pt);
      if ((pt = (struct Pt *) AllocRemember (&rememberkey, num_to_read,
                              MEMF_PUBLIC | MEMF_CLEAR)) == NULL)
         return (NOT_OK);
      if ((fin = fopen (map[i].mapname, "rb")) == NULL)
         return (NOT_OK);
      if ((num_read = fread (pt, sizeof (char), num_to_read, fin))
           != num_to_read)  {
         fclose (fin);
         return (NOT_OK);
      }
      fclose (fin);
      map[i].pt = pt;
   }
   return (OK);
}

/* ================================================================ */

void rotate_coords (double *clat, double *slat, double *clam,
                    double *slam, double *latout, double *lamout)
                                       /* gets values for rotated  */
{                                      /*   latitude and longitude */
   double x, y, z, xp, yp, zp;

   x = (*clat)*(*clam);                /* get original coordinates */
   y = (*clat)*(*slam);
   z = (*slat);
   xp = rm0[0]*x + rm0[1]*y + rm0[2]*z; /* rotate the corrdinates */
   yp = rm1[0]*x + rm1[1]*y + rm1[2]*z;
   zp = rm2[0]*x + rm2[1]*y + rm2[2]*z;
   (*latout) = asin (zp);              /* get new latitude and  */
   (*lamout) = atan2 (yp, xp);         /*   longitude (radians) */
   (*clat) = cos (*latout);
   (*slat) = sin (*latout);
   (*clam) = cos (*lamout);
   (*slam) = sin (*lamout);
   (*latout) /= RAD;                   /* convert to degrees */
   (*lamout) /= RAD;
}

/* ============================================================= */

void savemappic (void)                 /* moves map from screen to */
                                       /*   temporary bitmap       */
{
   ShowTitle (s, FALSE);               /* don't save window title bar */
   BltBitMapRastPort (rp->BitMap, 0, 0, &map_rp, 0, 0, WWIDTH, WHEIGHT,
                      0xc0);
   ShowTitle (s, title_toggle);
}

/* ================================================================ */

int save_to_disk (void)                /* saves map to disk */

{
   int result;
   IFFP iffresult;

   if ( (result = FileRequester (&filereq)) != TRUE) /* get file name */
      return (NOT_OK);
   ShowTitle (s, FALSE);               /* turn off title in saved file */
   iffresult = SaveWindowToIFF (pathname, w); /* save map to disk */
   ShowTitle (s, title_toggle);
   if (iffresult != IFF_OKAY)          /* set return code */
      return (NOT_OK);
   else
     return (OK);
}

/* ================================================================ */

void set_rotation_matrices (double latg, double lamg)
                                       /* computes rotation matrix */
{
   double c0, s0, c1, s1;

   if (latg == 0. && lamg==0.)  {      /* special case for centered map */
      centered = TRUE;
      rm0[0] = rm1[1] = rm2[2] = 1.;
      rm0[1] = rm0[2] = 0.;
      rm1[0] = rm1[2] = 0.;
      rm2[0] = rm2[1] = 0.;
   }
   else  {                             /* do the normal case */
      centered = FALSE;
      latg *= RAD;
      lamg *= RAD;
      c0 = cos(latg);
      s0 = sin(latg);
      c1 = cos(lamg);
      s1 = sin(lamg);
      rm0[0] = c0*c1;
      rm0[1] = c0*s1;
      rm0[2] = s0;
      rm1[0] = -s1;
      rm1[1] = c1;
      rm1[2] = 0.;
      rm2[0] = -s0*c1;
      rm2[1] = -s0*s1;
      rm2[2] = c0;
   }
}

/* ============================================================= */

void shadow (void)                     /* makes shadowed screens */
                                       /* shadows all colors except */
                                       /*   blue and black          */
{
   static char first = NOT_OK;
   static int blackcolor[DEPTH], disp;
   static int bluecolor[DEPTH];
   static unsigned int bitval[] = {1, 2, 4, 8, 16, 32, 64, 128};
   static short ytable[WHEIGHT];       /* offsets from beginning of */
                                       /*   each screen row         */
   int modb0, modb1, color[DEPTH];
   int bcolor[DEPTH];
   int j, k, k1, k2, m, bitlast;
   int test, t;
   unsigned int byte[DEPTH];
   unsigned int byteb0[DEPTH], byteb1[DEPTH];
 
   if (first==NOT_OK)  {               /* initialize color values once */
      first = OK;
      test = BLACK;
      blackcolor[0] = test&bitval[0];
      for (k=1; k<DEPTH; ++k)
         blackcolor[k] = (test&bitval[k]) >> k;
      test = BLUE;
      bluecolor[0] = test&bitval[0];
      for (k=1; k<DEPTH; ++k)
         bluecolor[k] = (test&bitval[k]) >> k;
      ytable[0] = 0;                   /* initialize screen offsets */
      for (k=1; k<WHEIGHT; ++k)
         ytable[k] = ytable[k-1] + ROWOFFSET;
      disp = SHADOW_DISP * ROWOFFSET;  /* offset to shadowed row */
   }
   for (k=ytable[0]; k<ytable[WHEIGHT-SHADOW_DISP-1]; k+=ROWOFFSET)  {
                                       /* do each row                  */
      k2 = k + disp;                   /* displacement to shadowed row */
      for (j=0; j<ROWOFFSET; ++j)  {   /* do each byte in row          */
         for (k1=0; k1<DEPTH; ++k1)  {
            byte[k1] = bp[k1][k+j];    /* current row */
            byteb0[k1] = bp[k1][k2+j]; /* shadowed row */
            byteb1[k1] = bp[k1][k2+j+1]; /* shadowed row, adjacent byte */
         }                             /* if last byte in row, don't   */
                                       /*   need adjacent bytes        */
         ((j<ROWOFFSET-1) ? (bitlast=0) : (bitlast=SHADOW_DISP));
         modb0 = NOT_OK;
         modb1 = NOT_OK;
         for (m=7; m>=bitlast; --m)  { /* check each bit, left to right */
            for (k1=0; k1<DEPTH; ++k1)  {
               t = byte[k1];
               color[k1] = BITVAL(t, m); /* current pixel color */
            }
            test = color[DEPTH-1];     /* build color value */
            for (k1=DEPTH-2; k1>=0; --k1)
               test = (test<<1) | color[k1];
            if (test != BLACK && test != BLUE)  {
               if (m>SHADOW_DISP-1)  {          /* get color of pixel */
                  for (k1=0; k1<DEPTH; ++k1)  { /*   in shadowed row  */
                     t = byteb0[k1];
                     bcolor[k1] = BITVAL(t, m-SHADOW_DISP);
                  }
               }
               else  {                 /* use adjacent byte */
                  for (k1=0; k1<DEPTH; ++k1)  {
                     t = byteb1[k1];
                     bcolor[k1] = BITVAL(t, m+8-SHADOW_DISP);
                  }
               }
               test = OK;
               for (k1=0; k1<DEPTH; ++k1)  { /* check if it is blue */
                  if (bcolor[k1] != bluecolor[k1])  {
                     test = NOT_OK;
                     break;
                  }
               }
               if (test == OK)  {      /* if blue, set color to black */
                  if (m>SHADOW_DISP-1)  {
                     for (k1=0; k1<DEPTH; ++k1)  {
                        t = byteb0[k1];
                        BITSTORE(t, m-SHADOW_DISP, blackcolor[k1]);
                        byteb0[k1] = t;
                     }
                     modb0 = OK;
                  }
                  else  {              /* use adjacent byte */
                     for (k1=0; k1<DEPTH; ++k1)  {
                        t = byteb1[k1];
                        BITSTORE(t, m+8-SHADOW_DISP, blackcolor[k1]);
                        byteb1[k1] = t;
                     }
                     modb1 = OK;
                  }
               }
            }
         }                             /* end bit test */
         if (modb0==OK)  {             /* restore only modified bytes */
            for (k1=0; k1<DEPTH; ++k1)
               bp[k1][k2+j] = byteb0[k1];
         }
         if (modb1==OK)  {
            for (k1=0; k1<DEPTH; ++k1)
               bp[k1][k2+j+1] = byteb1[k1];
         }
      }                                /* end of row */
   }                                   /* last row   */
}

/* ============================================================= */

void showmappic (void)                 /* moves map from temporary */
                                       /*   bitmap to screen       */
{
   if (got_flat_map != OK)             /* check if have already */
      fullmap (FLAT_TYPE);             /*   saved a flat map    */
   else
      BltBitMapRastPort (&map_bitmap, 0, 0, rp, 0, 0, WWIDTH, WHEIGHT, 0xc0);
}

/* ============================================================= */

void stars (void)                      /* draws stars into black background */

{
   static char init = FALSE;
   static int nmax = 150;              /* max number of stars */
   time_t systime;
   unsigned int t;
   int nstars;
   long x, y, color;

   if (!init)  {                       /* initialize seed value */
      init = TRUE;                     /*   with system time    */
      t = (int) time (&systime);
      srand (t);
   }
   SetAPen (rp, WHITE);
   nstars = 0;
   while (nstars < nmax)  {
      t = rand();
      x = t % WWIDTH + 1;
      t = rand();
      y = t % WHEIGHT + 1;
      if ((color = ReadPixel (rp, x, y)) == BLACK)  {
         WritePixel (rp, x, y);
         ++nstars;
      }
   }
}
