OVERLAY PROCEDURE FFT_MENU;

    { This procedure displays the driving menu for the FFT algorithm.
      The FFT_PROCEDURE procedure is called from this menu.
      The entire FFT program is a subprocedure to the FFT_MENU.
      This facilitates the concept of overlaying the programs }

  PROCEDURE FFT_PROCEDURE;

    CONST

    { "Tuning constants" to adjust accuracy, space requirements and
       speed of execution. }

      FRACTIONBIGGER =  0.2;
      WORLDX         =   31;      { SIZE OF THE WORLD }
      WORLDY         =   31;
      WORLDMAX       =   31;      { UPPER LIMIT TO EITHER WORLDX OR WORLDY }
      WORLDSIZE      = 1024;      { (WORLDX+1) * (WORLDY+1) }

    { The following unusual type declaration allows WORLD to be
      treated as either two-dimensional (D2) or one-dimensional (D1) }

    TYPE
      ARRAYD1 = ARRAY[1..WORLDSIZE] OF REAL;
      ARRAYD2 = ARRAY[0..WORLDX, 0..WORLDY] OF REAL;
      WORLDARRAY = RECORD
                     CASE BOOLEAN OF
                       TRUE : (D1 : ARRAYD1);
                       FALSE: (D2 : ARRAYD2)
                   END;

    { Global data known to main procedure and all sub procedures }

    VAR
      NPOINTS : INTEGER;          { NUMBER OF POINTS TO PROCESS }

      SUMOFWORLD  : REAL;
      LOWXWORLD   : REAL;
      HIGHXWORLD  : REAL;
      LOWYWORLD   : REAL;
      HIGHYWORLD  : REAL;

      WORLD       : WORLDARRAY;

    PROCEDURE OUTMATRIX (VAR OUT:ARRAYD2);

      { Writes the utilization distribution matrix out to the work
        file. This file is used later to create the maps. }

      VAR
        C     : INTEGER;
        I     : INTEGER;
        J     : INTEGER;

      BEGIN  { OUTMATRIX PROCEDURE }

        WRITELN;
        WRITELN ('OUTMATRIX');

        FOR I := 0 TO WORLDX DO
          BEGIN
            C := 0;
            FOR J := 0 TO WORLDY DO
              BEGIN
                C := C + 1;
                WRITE (FILEVAR5,' ',OUT[I,J]:10:3);
                IF C = 8 THEN
                  BEGIN
                    WRITELN (FILEVAR5);
                    C := 0
                  END;
              END;  { FOR J = 0 TO WORLDY }

            IF C <> 0 THEN WRITELN (FILEVAR5);

          END; { FOR I = 0 TO WORLDX }

      END;  { OUTMATRIX PROCEDURE }

    PROCEDURE MAP;

      { Calculates the map index from a UD which has first been sorted
        by SHELLSORTUD.
        2) Extracts the sorted value at the map index for isoline plotting. }

      CONST
        HEADING1 = '    MAP %      MAP         WORLD      PERCENT OF';
        HEADING2 = '      TOTAL       NUMBER OF';
        HEADING3 = '              VALUE        VALUE      TOTAL AREA';
        HEADING4 = '       AREA        SQUARES';

      VAR
        I     : INTEGER;
        J     : INTEGER;
        NSQRS : INTEGER;

        PROP         : REAL;
        AREAOFWORLD  : REAL;
        LIMIT        : REAL;
        SUM          : REAL;
        PVALUE       : REAL;
        MAP          : REAL;

      BEGIN  { MAP PROCEDURE }


        AREAOFWORLD := (HIGHXWORLD-LOWXWORLD) * (HIGHYWORLD-LOWYWORLD);

        { PRINT THE MAP HEADINGS }

        IF UPCASE(PRINTCHAR) IN ['D','B'] THEN
          BEGIN
            CLRSCR;
            GOTOXY(1,1);
            WRITELN (HEADING1,HEADING2);
            WRITELN (HEADING3,HEADING4);
          END;  { IF PRINTCHAR = D OR B }

        IF UPCASE(PRINTCHAR) IN ['P','B'] THEN
          BEGIN
            WRITELN (LST,HEADING1,HEADING2);
            WRITELN (LST,HEADING3,HEADING4);
          END;  { IF PRINTCHAR = P OR B }

        J := 5;
        WHILE J < 100 DO
          BEGIN
            PVALUE:= J/100;
            J := J + 5;
            LIMIT := (1.0-PVALUE) * SUMOFWORLD;
            I := 0;
            SUM := 0.0;

            REPEAT
              I := I + 1;
              SUM := SUM + WORLD.D1[I];
            UNTIL ( SUM > LIMIT ) OR ( I=WORLDSIZE );


            IF SUM > LIMIT THEN
              BEGIN
                I := I - 1;
                MAP := (WORLDSIZE-I) * AREAOFWORLD / WORLDSIZE;
                PROP:= (WORLDSIZE-I)/WORLDSIZE;
                NSQRS:= WORLDSIZE-I;

                { WRITE / DISPLAY THE TABLE INFORMATION }

                IF UPCASE(PRINTCHAR) IN ['D','B'] THEN
                  BEGIN
                    WRITE ('   (',PVALUE:3:2,')   ',MAP:8:5,'    ');
                    WRITE (WORLD.D1[I]:10:6,'     ',PROP:6:4,'      ');
                    WRITE (AREAOFWORLD:10:6,'       ',NSQRS:3);
                    WRITELN;
                  END;  { IF PRINTCHAR = D OR B }
                IF UPCASE(PRINTCHAR) IN ['P','B'] THEN
                  BEGIN
                    WRITE (LST,'   (',PVALUE:3:2,')   ',MAP:8:5,'    ');
                    WRITE (LST,WORLD.D1[I]:10:6,'     ',PROP:6:4,'      ');
                    WRITE (LST,AREAOFWORLD:10:6,'       ',NSQRS:3);
                    WRITELN (LST);
                  END;  { IF PRINTCHAR = P OR B }

              END;  { IF SUM > LIMIT ... }

          END;  { WHILE J < 100 }

        KEYBOARD_PAUSE

      END;  { MAP PROCEDURE }

  PROCEDURE SHELLSORTUD (VAR WORLD:ARRAYD1);

    { SHELLSORT.  Treats the UD stored in world as a one dimensional
     array and sorts it in increasing order. This is necessary before
     calling map. Adapted from KNUTH xxxx        }

    CONST
      T = 4;
      N = WORLDSIZE;

    TYPE
      INDEX =-15..WORLDSIZE;
      ITEM = REAL;

    VAR
      I       : INDEX;
      J       : INDEX;
      K       : INDEX;
      S       : INDEX;

      X       : ITEM;

      M       : 1..T;

      ARAY    : ARRAY[INDEX] OF REAL;

      H       : ARRAY [1..T] OF INTEGER;

    BEGIN

      WRITELN('SORTING');

      FOR I:= 1 TO WORLDSIZE DO
        ARAY[I] := WORLD[I];
      H[1]:=15;
      H[2]:=7;
      H[3]:=3;
      H[4]:=1;

      { SORT }

      FOR M := 1 TO T DO
        BEGIN
          K := H[M];
          S := -K;
          FOR I := K+1 TO N DO
            BEGIN
              X := ARAY[I];
              J := I-K;
              IF S=0 THEN S := -K;
              S := S+1;
              ARAY[S] :=X;
              WHILE X < ARAY[J] DO
                BEGIN
                  ARAY[J+K] := ARAY[J];
                  J := J-K;
                END;
              ARAY[J+K] := X;
            END;  { FOR I = K+1 TO N }

        END; { FOR M = 1 TO T }

      FOR I := 1 TO WORLDSIZE DO
        WORLD[I] := ARAY[I];
      SUMOFWORLD := 0.0;
      FOR I := 1 TO WORLDSIZE DO
        SUMOFWORLD := SUMOFWORLD + WORLD[I];

    END;  { SHELLSORTUD }

  PROCEDURE DENSITY;

    { Estimates the UD from the ovservations stored in variable
      WORLD using the FOURIER TRANSFROM METHOD (ANDERSON, 1980).
      WORLD and NPOINTS are used. }

    CONST
      { CUTOFFCOUNT should normally be set to 2.  When a CUTOFFCOUNT
     by CUTOFFCOUNT square of FOURIER COEFFICIENTS drops below
     cutoff, they are set to 0.  Detailed explanation is found at the
     end of APPENDIX A (ANDERSON, 1981) }

      CUTOFFCOUNT = 2;

    TYPE
      COMPLEX = RECORD
                  RE : REAL;
                  IM : REAL;
                END;

      { DIRECT = FORWARD FFT, INVERSE = INVERSE FFT }

       DIRECTINVERSE = (DIRECT, INVERSE);
       XROWYCOLUMN = (XROW, YCOLUMN);
       INDEX = RECORD
                 L : INTEGER;
                 N : INTEGER;
               END;

    VAR
      X       : INTEGER;
      Y       : INTEGER;
      NSWAP   : INTEGER;

      NSQUARED : REAL;

      IMAGWORLD : ARRAYD2;
      COEFFTABLE : ARRAY[0..WORLDMAX] OF COMPLEX;
      SWAPINDEX : ARRAY[0..WORLDMAX] OF INDEX;

    PROCEDURE FFT (FFTDIRECTION:XROWYCOLUMN;
                   ROWORCOLUMN:INTEGER);

      { RECURSIVE FAST FOURIER TRANSFORM.  SEE FIG. 6.10 P297
        OPPENHEIM & SCHAFER (1975) }

    VAR
      LENGTH      : INTEGER;
      LENGTHOVER2 : INTEGER;

      PROCEDURE SUBDIVIDE (START, HALFSIZE:INTEGER);

        (*RECURSIVELY SUBDIVIDE DATA*)

        PROCEDURE SWARM;

          { CALCULATE A SWARM OF BUTTERFLIES }

        VAR
          RINC    : INTEGER;
          R       : INTEGER;
          P       : INTEGER;
          I1      : INTEGER;
          I2      : INTEGER;
          J1      : INTEGER;
          J2      : INTEGER;

          XP          : COMPLEX;
          XQ          : COMPLEX;
          TERM        : COMPLEX;
          COEFFICIENT : COMPLEX;

         BEGIN  { SWARM }

           RINC := LENGTHOVER2 DIV HALFSIZE;
           R := 0;
           FOR P := START TO (START + HALFSIZE - 1) DO
             BEGIN
               COEFFICIENT := COEFFTABLE[R];
               CASE FFTDIRECTION OF
                 XROW : BEGIN
                        I1 := P;
                        J1 := ROWORCOLUMN;
                        I2 := P + HALFSIZE;
                        J2 := ROWORCOLUMN
                        END;
                YCOLUMN : BEGIN
                        I1 := ROWORCOLUMN;
                        J1 := P;
                        I2 := ROWORCOLUMN;
                        J2 := P + HALFSIZE
                        END;
                 END; { CASE FFTDIRECTION }

               XP.RE := WORLD.D2[I1,J1];
               XP.IM := IMAGWORLD[I1,J1];
               XQ.RE := WORLD.D2[I2,J2];
               XQ.IM := IMAGWORLD[I2,J2];
               TERM.RE := COEFFICIENT.RE*XQ.RE - COEFFICIENT.IM*XQ.IM;
               TERM.IM := COEFFICIENT.RE*XQ.IM + COEFFICIENT.IM*XQ.RE;
               WORLD.D2[I1,J1] := XP.RE + TERM.RE;
               IMAGWORLD[I1,J1] := XP.IM + TERM.IM;
               WORLD.D2[I2,J2] := XP.RE - TERM.RE;
               IMAGWORLD[I2,J2] := XP.IM - TERM.IM;
               R := R + RINC

             END;  { FOR P = START TO ... }

         END;  { SWARM }

       BEGIN  { SUBDIVIDE PROCEDURE }

         IF HALFSIZE > 1 THEN
           BEGIN
             SUBDIVIDE (START, HALFSIZE DIV 2);
             SUBDIVIDE (START+HALFSIZE, HALFSIZE DIV 2)
           END;

        SWARM;

      END;  { SUBDIVIDE PROCEDURE }

      PROCEDURE BITREVERSE;
      VAR
        N, I1, I2, J1, J2 : INTEGER;
        SWAP : REAL;

       BEGIN  { BITREVERSE PROCEDURE }

         FOR N := 0 TO NSWAP DO
           BEGIN
             CASE FFTDIRECTION OF
               XROW : BEGIN
                      I1 := SWAPINDEX[N].L;
                      J1 := ROWORCOLUMN;
                      I2 := SWAPINDEX[N].N;
                      J2 := ROWORCOLUMN;
                      END;
              YCOLUMN : BEGIN
                      I1 := ROWORCOLUMN;
                      J1 := SWAPINDEX[N].L;
                      I2 := ROWORCOLUMN;
                      J2 := SWAPINDEX[N].N;
                      END;
             END;  { CASE FFTDIRECTION }

             SWAP := WORLD.D2[I1,J1];
             WORLD.D2[I1,J1] := WORLD.D2[I2,J2];
             WORLD.D2[I2,J2] := SWAP;
             SWAP := IMAGWORLD[I1,J1];
             IMAGWORLD[I1,J1] := IMAGWORLD[I2,J2];
             IMAGWORLD[I2,J2] := SWAP ;

           END;  { FOR I = 0 TO NSWAP }

       END;  { BITREVERSE PROCEDURE }

     BEGIN  { FFT PROCEDURE }

       WRITE ('f.');
       CASE FFTDIRECTION OF
         XROW : LENGTH := WORLDX +1;
         YCOLUMN : LENGTH := WORLDY +1
       END;  { CASE FFTDIRECTION }

       BITREVERSE;

       LENGTHOVER2 := LENGTH DIV 2;

       SUBDIVIDE (0, LENGTHOVER2);

     END;  { FFT PROCEDURE }

    PROCEDURE SETUP (KIND : DIRECTINVERSE;
                     SIZE : INTEGER);

      CONST
        TWOPI = 6.2831853;

      VAR
        R        : INTEGER;
        N        : INTEGER;
        L        : INTEGER;
        LOG2SIZE : INTEGER;

        TWOPIOVERSIZE : REAL;
        THETA         : REAL;

      FUNCTION REVERSE (N:INTEGER):INTEGER;

        { BIT REVERSE N }

        VAR
          I      : INTEGER;
          RESULT : INTEGER;

       BEGIN  { REVERSE FUNCTION }

         WRITE ('r');
         RESULT := 0;
         FOR I := 1 TO LOG2SIZE DO
           BEGIN
             RESULT := RESULT*2;
             IF (N MOD 2) <> 0  THEN RESULT := RESULT +1;
             N := N DIV 2;
           END;

         REVERSE := RESULT;

       END;  { REVERSE FUNCTION }

     BEGIN  { SETUP PROCEDURE }

       { CALCULATE SINE AND COSINE COEFFICIENTS }

       WRITELN ('BEGINNING SETUP');

       TWOPIOVERSIZE := TWOPI / SIZE;
       FOR R := 0 TO (SIZE - 1) DO
         BEGIN
           THETA := R * TWOPIOVERSIZE;
           COEFFTABLE[R].RE := COS (THETA);
           CASE KIND OF
             DIRECT  : COEFFTABLE[R].IM :=  SIN(THETA);
             INVERSE : COEFFTABLE[R].IM := -SIN (THETA)
           END; { CASE KIND OF }

         END; { FOR R = O TO SIZE-1 }

       { CALCULATE LOG BASE 2 OF SIZE }

       N := SIZE;
       LOG2SIZE := 0;
         REPEAT
           N := N DIV 2;
           LOG2SIZE := LOG2SIZE +1
         UNTIL N = 1;

       { CALCULATE COEFFICIENTS FOR BIT REVERSAL }

       NSWAP := -1;
       FOR N := 0 TO (SIZE-1) DO
         BEGIN
           L := REVERSE(N);
           IF L > N THEN
             BEGIN
               NSWAP := NSWAP + 1;
               SWAPINDEX[NSWAP].L := L;
               SWAPINDEX[NSWAP].N := N
             END; { IF L > N }

         END;  { FOR N = 0 TO SIZE - 1 }

         WRITELN;

     END;  { SETUP PROCEDURE }

    PROCEDURE EXCISE (XBOTTOM,XTOP,YBOTTOM,YTOP:INTEGER);

      { Selectively excises FOURIER COEFFICIENTS within the rectangular
       region xbottom to xtop and ybottom to ytop.  If any
       CUTOFFCOUNT by CUTOFFCOUNT sized square of FOURIER COEFFICIENTS
       within the region drops below cutoff then all higher frequency
       coefficients in the region are set to zero.

       I'M NOT HAPPY WITH THIS PROCEDURE.  IT IS TOO LONG AND
       COMPLICATED CONSIDERING WHAT IT DOES.  IT COULD PROBABLY
       BE REWRITTEN TO IMPROVE CLARITY.  ALSO, IT MIGHT BE POSSIBLE
       TO MAKE USE OF THE FACT THAT OF THE 4 CALLS TO EXCISE, 2
       CARRY OUT IDENTICAL CALCULATIONS IN DIFFERENT REGIONS. }

    TYPE
      XY = RECORD
             X : INTEGER;
             Y : INTEGER;
           END;

    VAR
      I       : INTEGER;
      X       : INTEGER;
      Y       : INTEGER;
      LAST    : INTEGER;
      XINC    : INTEGER;
      YINC    : INTEGER;
      OLDXTOP : INTEGER;
      OLDYTOP : INTEGER;

      CUTOFF  : REAL;

      { STORAGE FOR THE X,Y LOCATIONS ABOVE WHICH THE FOURIER
       COEFFICIENTS MUST BE SET TO ZERO. }

      STOREXY : ARRAY[1..WORLDMAX] OF XY;

      FUNCTION ZEROSQUARE (X,Y:INTEGER):BOOLEAN;

        { Checks to see if the square X to X+CUTOFFCOUNT,
         Y TO Y+CUTOFFCOUNT has dropped below cutoff and needs
         to be set to zero. }

      VAR
        XEND  : INTEGER;
        YEND  : INTEGER;
        OLDX  : INTEGER;

      BEGIN  { ZEROSQUARE FUNCTION }

        ZEROSQUARE := TRUE;
        XEND := X + XINC*CUTOFFCOUNT;
        YEND := Y + YINC*CUTOFFCOUNT;
        OLDX := X;
        REPEAT
          X := OLDX;
          REPEAT
            IF ( SQR(WORLD.D2[X,Y]) + SQR(IMAGWORLD[X,Y]) ) >= CUTOFF
              THEN ZEROSQUARE := FALSE;
            X := X+XINC;
          UNTIL X = XEND;
          Y := Y + YINC;
        UNTIL Y = YEND;

      END; { ZEROSQUARE FUNCTION }

    BEGIN  { EXCISE }

      WRITELN;
      WRITELN ('EXCISE');

      CUTOFF := ( 2 / (NPOINTS+1) ) * NPOINTS * NPOINTS;

      { NOTE THAT CUTOFF IS ADJUSTED BY SQR(NPOINTS) BECAUSE
        THE FOURIER TRANSFORM WAS NOT DIVIDED BY NPOINTS }

      IF XTOP > XBOTTOM
        THEN XINC := 1
        ELSE XINC := -1;
      IF YTOP > YBOTTOM
        THEN YINC := 1
        ELSE YINC := -1;
      LAST := 0;

      { SAVE OLD TOP FOR LATER }

      OLDXTOP := XTOP;
      OLDYTOP := YTOP;

      { ADJUST TOP BECAUSE THE HIGHEST CUTOFFCOUNT BY CUTOFFCOUNT
        SQUARE SHOULD NOT BE ABOVE ORIGINAL XTOP AND YTOP. }

      XTOP := XTOP + XINC*(2-CUTOFFCOUNT);
      YTOP := YTOP + YINC*(2-CUTOFFCOUNT);
      Y := YBOTTOM;

      { SCAN THROUGH THE WORLD CALCULATING THE X,Y COORDINATES
        ABOVE WHICH THE FOURIER COEFFICIENTS MUST BE SET TO ZERO. }

      WHILE Y <> YTOP DO
        BEGIN
          X := XBOTTOM;
          WHILE X <> XTOP DO
            BEGIN
              IF ( SQR(WORLD.D2[X,Y]) + SQR(IMAGWORLD[X,Y]) ) < CUTOFF THEN
                BEGIN
                  WORLD.D2[X,Y] := 0.0;
                  IMAGWORLD[X,Y] := 0.0;
                  IF ZEROSQUARE(X,Y) THEN
                    BEGIN
                      LAST := LAST + 1;
                      STOREXY[LAST].X := X;
                      STOREXY[LAST].Y := Y;
                      XTOP := X
                    END
                  ELSE
                    X := X + XINC;
                END
              ELSE
                X := X + XINC;
            END;

          Y := Y + YINC
        END;

      { RESTORE OLD TOP }

      XTOP := OLDXTOP + XINC;
      YTOP := OLDYTOP + YINC;

      { SET COEFFICIENTS TO ZERO *}

      FOR I := 1 TO LAST DO
        BEGIN
          Y := STOREXY[I].Y;
          WHILE Y <> YTOP DO
            BEGIN
              X := STOREXY[I].X;
              WHILE (X*XINC) < (XTOP*XINC) DO
                BEGIN
                  WORLD.D2[X,Y] := 0.0;
                  IMAGWORLD[X,Y] := 0.0;
                  X := X + XINC
                END;
              Y := Y + YINC
            END; { WHILE Y <> YTOP }

          XTOP := STOREXY[I].X;

        END; { FOR I = 1 TO LAST }

      END; { EXCISE PROCEDURE }

    BEGIN  { DENSITY PROCEDURE }

      WRITELN ('BEGINNING DENSITY ');

      { ZERO OUT IMIGINARY DATA }

       FOR X :=0 TO WORLDX DO
         FOR Y :=0 TO WORLDY DO
           IMAGWORLD[X,Y] := 0;

      { DO 2 DIMENSIONAL DIRECT (OR FORWARD) FFT }
      { DO ROWS }

      SETUP (DIRECT, WORLDX+1 );

      FOR X :=0 TO WORLDX DO
        FFT (XROW, X);

      { THEN COLUMNS }

      IF WORLDY <> WORLDX THEN
        SETUP (DIRECT, WORLDY+1 );

      FOR Y := 0 TO WORLDY DO
        FFT (YCOLUMN, Y);

      { FILTER BY EXCISING COEFFICIENTS WITHIN SPECIFIED REGION }

      EXCISE ( 0,      (WORLDX DIV 2),      0,      (WORLDY DIV 2)     );
      EXCISE ( WORLDX, ((WORLDX+1) DIV 2),  0,      (WORLDY DIV 2)     );
      EXCISE ( 0,      (WORLDX DIV 2),      WORLDY, ((WORLDY+1) DIV 2) );
      EXCISE ( WORLDX, ((WORLDX+1) DIV 2),  WORLDY, ((WORLDY+1) DIV 2) );

      { THE DIRECT FFT DOES NOT DIVIDE BY N SO DO IT NOW }

      NSQUARED := (WORLDX+1) * (WORLDY+1);
      FOR X := 0 TO WORLDX DO
        FOR Y := 0 TO WORLDY DO
          BEGIN
            WORLD.D2[X,Y] := WORLD.D2[X,Y] / NSQUARED;
            IMAGWORLD[X,Y] := IMAGWORLD[X,Y] / NSQUARED
          END;

      { DO TWO DIMENSTIONAL INVERSE FFT }

     SETUP (INVERSE, WORLDY+1 ); (*DO COLUMNS*)
     FOR Y := 0 TO WORLDY DO
     FFT (YCOLUMN, Y);
     IF WORLDX<>WORLDY  (*THEN ROWS*)
      THEN
      SETUP (INVERSE, WORLDX+1 );
     FOR X := 0 TO WORLDX DO
     FFT (XROW, X);
   END (*DENSITY*);

  PROCEDURE READDATA;

    { READS DATA (X,Y LOCATIONS) FROM INPUT DATA FILE. }

    CONST
      MAXWARN = 5;                { MAXIMUM NUMBER OF WARNINGS ISSUED }

    VAR
      XINDEX    : INTEGER;
      YINDEX    : INTEGER;
      I         : INTEGER;
      ERRORCOUNT : INTEGER;       { NUMBER OF ERRORS THAT HAVE OCCURRED }
      NPOINTS   : INTEGER;        { NUMBER OF OBSERVATIONS READ }

    BEGIN  { READDATA PROCEDURE }

      { Load the min and max values from second and third records of data }

      WRITELN;
      WRITELN('Loading min and max values.');

     (*INITIALIZE VARIABLES*)

      SEEK (FILEVAR2,1);
      READ (FILEVAR2,FILE2REC);

      XLOW  := FILE2REC.X;
      YLOW  := FILE2REC.Y;
      ZLOW  := FILE2REC.Z;
      NPOINTS := FILE2REC.RECORD_NO;

      READ (FILEVAR2,FILE2REC);        { GET THE MAXIMUM VALUES }

      XHIGH := FILE2REC.X;
      YHIGH := FILE2REC.Y;
      ZHIGH := FILE2REC.Z;

      IF XLOW = XHIGH THEN             { HOME RANGE IS A VERTICAL LINE }
        BEGIN
          XLOW := XLOW - 1.0;          { OFFSET THE GRID BOUNDRIES SO PROGRAM }
          XHIGH := XHIGH + 1.0;        { WILL WORK. }
          LINE_OUT(1,24,SPACE79);
          LINE_OUT(1,25,SPACE79);
          LINE_OUT(1,24,'WARNING: All points have same X location');
          GOTOXY(1,25);
          KEYBOARD_PAUSE;
        END;

      IF YLOW = YHIGH THEN             { HOME RANGE IS A HORIZONTAL LINE }
        BEGIN
          YLOW := YLOW - 1.0;
          YHIGH := YHIGH + 1.0;
          LINE_OUT(1,24,SPACE79);
          LINE_OUT(1,25,SPACE79);
          LINE_OUT(1,24,'WARNING: All points have the same Y location');
          GOTOXY(1,25);
          KEYBOARD_PAUSE;
        END;  { YLOW = YHIGH }

      { ASK USER IF THEY WANT TO USE THE STUDY AREA OVERLAY TO DEFINE GRID }

      IF STUDYFILE <> '' THEN
       BEGIN
         LINE_OUT(1,25,SPACE79);
         LINE_OUT(1,25,'Do you want to use the STUDY FILE for grid boundry definition? (Y|N) ');
         KEYVALUE := ' ';
         WHILE NOT (UPCASE(KEYVALUE) IN ['Y','N']) DO
           READ    (KBD,KEYVALUE);

         KEYVALUE := UPCASE(KEYVALUE);

         IF KEYVALUE = 'Y' THEN
           BEGIN
             ASSIGN (FILEVAR3,STUDYFILE);
             RESET  (FILEVAR3);
             SEEK   (FILEVAR3,1);
             READ   (FILEVAR3,FILE3REC);

             XLOW := MIN_VAL(FILE3REC.X1,XLOW);
             YLOW := MIN_VAL(FILE3REC.Y1,YLOW);
             ZLOW := MIN_VAL(FILE3REC.Z1,ZLOW);
             XHIGH := MAX_VAL(FILE3REC.X2,XHIGH);
             YHIGH := MAX_VAL(FILE3REC.Y2,YHIGH);
             ZHIGH:= MAX_VAL(FILE3REC.Z2,ZHIGH);

           END;  { IF KEYVALUE = Y }

       END;  { IF STUDYFILE <> '' };

      { If a study area is used as input to the user definition of the boundry
        then we need to determine the four extreme points of the study area. }

      { <<<<<<<<<<<< Later, install code to find four extremes of the study
        area file. >>>>>>>>>>>>>>>>>>>>}

      { Determine the delta x and delta y for the grid }

      CLRSCR;
      WRITELN;
      WRITELN('Outer limits of the grid are: ');
      WRITELN('   X: ',XLOW:10:5,' TO ',XHIGH:10:5);
      WRITELN('   Y: ',YLOW:10:5,' TO ',YHIGH:10:5);
      WRITELN('There are ',NPOINTS,' data points.');

 {    CLRSCR;   }
      LOWXWORLD  := XLOW  - (XHIGH-XLOW) * FRACTIONBIGGER;
      HIGHXWORLD := XHIGH + (XHIGH-XLOW) * FRACTIONBIGGER;
      LOWYWORLD  := YLOW  - (YHIGH-YLOW) * FRACTIONBIGGER;
      HIGHYWORLD := YHIGH + (YHIGH-YLOW) * FRACTIONBIGGER;

      { WORLDX AND WORLDY ARE THE SAME AS GRIDSIZE IN HISTOGRAM }

      XDELTA := (HIGHXWORLD - LOWXWORLD) / WORLDX;
      YDELTA := (HIGHYWORLD - LOWYWORLD) / WORLDY;

      NPOINTS := 0;
      ERRORCOUNT := 1;

      { SET WORLD GRID VALUES TO ALL 0.0 };

      FOR I := 1 TO WORLDSIZE DO
        WORLD.D1[I] := 0.0;

      { Second pass is to put the elements into the grid cells. }

      WRITELN;
      WRITELN ('SECOND PASS, GETTING FREQUENCY OF CELL COUNTS' );

      SEEK (FILEVAR2,3);                { SET PAST THE FILE HEADER }

      WHILE NOT EOF(FILEVAR2) DO
        BEGIN
          READ  (FILEVAR2,FILE2REC);
          WITH FILE2REC DO
            BEGIN
              XINDEX := ROUND ((X-LOWXWORLD) / XDELTA);
              YINDEX := ROUND ((Y-LOWYWORLD) / YDELTA);

              { IF POINT IS WITH IN THE GRID, ADD IT TO THE GRID FREQUENCIES }

              IF (XINDEX IN [0..WORLDX]) AND (YINDEX IN [0..WORLDX]) THEN
                BEGIN
                  NPOINTS := NPOINTS + 1;
                  IF ( NPOINTS = 1 ) OR ( X < XLOW ) THEN XLOW  := X;
                  IF ( NPOINTS = 1 ) OR ( X > XHIGH) THEN XHIGH := X;
                  IF ( NPOINTS = 1 ) OR ( Y < YLOW ) THEN YLOW  := Y;
                  IF ( NPOINTS = 1 ) OR ( Y > YHIGH) THEN YHIGH := Y;
                  WORLD.D2[XINDEX,YINDEX] := WORLD.D2[XINDEX,YINDEX] + 1.0
                END  { IF INDEXES WITHIN THE WORLD }
              ELSE
                IF ERRORCOUNT <= MAXWARN THEN
                  BEGIN

                    { DISPLAY/LIST ERROR MESSAGES }

                    IF UPCASE(PRINTCHAR) IN ['D','B'] THEN
                      WRITELN (OUTPUT,' ?HOMERANGE-W-LOCATION (', X:1:3, ',',
                          Y:1:3, ') IGNORED; IT IS OUTSIDE OF WORLD');
                    IF UPCASE(PRINTCHAR) IN ['P','B'] THEN
                      WRITELN (LST,' ?HOMERANGE-W-LOCATION (', X:1:3, ',',
                          Y:1:3, ') IGNORED; IT IS OUTSIDE OF WORLD');

                    ERRORCOUNT := ERRORCOUNT + 1;

                    IF ERRORCOUNT > MAXWARN THEN
                      BEGIN

                        { DISPLAY/LIST FINAL WARNING }

                        IF UPCASE(PRINTCHAR) IN ['P','B'] THEN
                          WRITELN (OUTPUT,' ?HOMERANGE-W-TOO MANY WARNINGS; ',
                           'FUTURE WARNINGS WON''T BE PRINTED');
                        IF UPCASE(PRINTCHAR) IN ['P','B'] THEN
                          WRITELN (LST,' ?HOMERANGE-W-TOO MANY WARNINGS; ',
                           'FUTURE WARNINGS WON''T BE PRINTED');
                      END;

                  END;  { IF ERRORCOUNT <= MAXWARN }

            END; { WITH FILE2REC DO }

        END;  { WHILE NOT EOF(FILEVAR2) }

      IF XLOW = XHIGH THEN
        BEGIN
          XLOW  := XLOW  - 1.0;
          XHIGH := XHIGH + 1.0;
        END;
      IF YLOW = YHIGH THEN
        BEGIN
          YLOW  := YLOW  - 1.0;
          YHIGH := YHIGH + 1.0;
        END;
      AREA := (XHIGH-XLOW) * (YHIGH-YLOW);
      AREA := AREA / (( HIGHXWORLD - LOWXWORLD ) * ( HIGHYWORLD - LOWYWORLD ));

      IF (AREA < 0.2) THEN
        BEGIN

          { DISPLAY / LIST MESSAGES }

          IF UPCASE(PRINTCHAR) IN ['D','B'] THEN
            WRITELN (OUTPUT,' ?HOMERANGE-W-OBSERVATIONS FELL WITHIN ONLY ',
                   (AREA*100.0):1:1,'& OF THE WORLD');
          IF UPCASE(PRINTCHAR) IN ['P','B'] THEN
            WRITELN (LST,' ?HOMERANGE-W-OBSERVATIONS FELL WITHIN ONLY ',
                   (AREA*100.0):1:1,'& OF THE WORLD');

        END;  { IF AREA < .2 }

     END;  { READDATA PROCEDURE }


  BEGIN  { FFT_PROGRAM PROCEDURE }

    { OPEN AND SET THE FILES }

    ASSIGN  (FILEVAR2,FILEOUT);
    RESET   (FILEVAR2);

    { READ THE DATA }

    READDATA;
    WRITELN ('END OF READDATA');

    { ESTIMATE THE UD }

    DENSITY;

    { The UD is now available in WORLD.D2 to be output if desired  }

    IF UDRESTART <> '' THEN
      BEGIN
        ASSIGN (FILEVAR5,UDRESTART);
        REWRITE(FILEVAR5);
        UDOPEN := TRUE;
        OUTMATRIX (WORLD.D2);
      END;

    SHELLSORTUD (WORLD.D1);

    { MAP(P) may be called for other values of P if desired  }

    MAP;

    KEYVALUE := 'R';

    CLOSE (FILEVAR2);
    IF UDOPEN THEN CLOSE (FILEVAR5);

  END;  { FFT_PROCEDURE PROCEDURE }

  BEGIN  { FFT_MENU PROCEDURE }

    { Display the options menu for FFT_PROCEDURE }

    KEYVALUE := 'R';

    WHILE KEYVALUE = 'R' DO
    BEGIN

    CLRSCR;
    DISPLAY_COLOR(CYAN);

    LINE_OUT(20,2,'(9) Fast Fourier Transformation (no graphics)');
    LINE_OUT(10,4,'1  Data file:');
    LINE_OUT(10,6,'2  Study area file:');
    LINE_OUT(10,8,'3  ');
    LINE_OUT(10,10,'4  Text output displayed on:');
    LINE_OUT(10,12,'5  ');
    LINE_OUT(10,14,'6  Restart file:');
    LINE_OUT(10,16,'7  ');
    LINE_OUT(10,18,'8  ');

    TEXTCOLOR(YELLOW);
    LINE_OUT(24,4,FILEOUT);
    LINE_OUT(30,6,STUDYFILE);
    LINE_OUT(27,14,UDRESTART);

    CASE UPCASE(PRINTCHAR) OF
      'D' : PRINTSTR := 'DISPLAY ONLY';
      'P' : PRINTSTR := 'PRINTER ONLY';
      'B' : PRINTSTR := 'BOTH DISPLAY AND PRINTER';
    END;
    LINE_OUT(39,10,PRINTSTR);

    DISPLAY_COLOR(RED);

    LINE_OUT(7,21,'P  Process Data');
    LINE_OUT(37,21,'E  Exit to Main Menu');
    TEXTCOLOR(YELLOW);
    GOTOXY(1,24);

    KEYVALUE := ' ';

    WHILE NOT (UPCASE(KEYVALUE) IN ['E','R']) DO
      BEGIN

        KEYVALUE := ' ';
        WHILE NOT (UPCASE(KEYVALUE) IN ['1','2','4','6','E','P']) DO
          READ (KBD,KEYVALUE);
        KEYVALUE := UPCASE(KEYVALUE);

        GOTOXY(1,23);
        DELLINE;
        DELLINE;

        CASE KEYVALUE OF
          '1': MENU_CALL(1);
          '2': MENU_CALL(2);
          '4': MENU_CALL(4);
          '6': BEGIN
               LINE_OUT(1,23,'Enter the name of the FFT RESTART file ');
               READLN  (INPUT,UDRESTART);
               GOTOXY(1,23);
               DELLINE;
               CAPIT(UDRESTART);
               LINE_OUT(27,14,UDRESTART);
               END;
          'P': BEGIN
               KEEP_GOING := TRUE;

               IF FILEOUT = '' THEN
                 BEGIN
                   LINE_OUT(1,23,'The input data set must be defined.');
                   KEEP_GOING := FALSE;
                 END;

               IF (KEEP_GOING) AND (NOT EXIST(FILEOUT)) THEN
                 BEGIN
                   GOTOXY(1,23);
                   WRITE('Formatted data file ',FILEOUT,' does not exist.');
                   KEEP_GOING := FALSE;
                 END;

               IF KEEP_GOING THEN
                 BEGIN
                   VERIFY_DATA_FILE;
                   IF NOT KEEP_GOING THEN
                     BEGIN
                       GOTOXY(1,23);
                       WRITE('The input file ',FILEOUT,' is not a formatted data file.');
                       IF (XLOW = -5) AND (YLOW = -3) AND (ZLOW = -7)
                         THEN LINE_OUT(1,24,'It is a formatted STUDY AREA file.')
                         ELSE LINE_OUT(1,24,'It may be an unformatted data file or a version 1.0 file.');
                     END;
                 END;

               IF (KEEP_GOING) AND (STUDYFILE <> '') THEN
                 IF NOT EXIST(STUDYFILE) THEN
                   BEGIN
                     GOTOXY(1,23);
                     WRITE ('Formatted study area file ',STUDYFILE,' does not exist.');
                     KEEP_GOING := FALSE;
                   END;

               IF (KEEP_GOING) AND (STUDYFILE <> '') THEN
                 BEGIN
                   VERIFY_STUDY_AREA_FILE;
                   IF NOT KEEP_GOING THEN
                     BEGIN
                       GOTOXY(1,23);
                       WRITE ('The study area file ',STUDYFILE,'  is not a formatted study area file.');
                       IF (SXLOW = -1) AND (SYLOW = -2) AND (SXHIGH = -3)
                         THEN LINE_OUT(1,24,'It is a formatted INPUT DATA file.')
                         ELSE LINE_OUT(1,24,'It may be an unformatted data file or unformatted study area file.');
                       END;
                     END;

               IF KEEP_GOING THEN
                 BEGIN
                   CLRSCR;
                   FFT_PROCEDURE;
                 END;

               END;  { CASE 'P' }

        END;  { CASE KEYVALUE OF }

        GOTOXY(1,25);

      END;  { WHILE NOT KEYVALUE IN [E,R] }

    END;  { WHILE KEYVALUE = 'R' }

    KEYVALUE := ' ';

  END;  { FFT_MENU PROCEDURE }

