static char rcsid[] = "$Id: ciamap.c,v 3.10 1993/01/05 01:37:03 putz Exp $";
/*****************************************************************************
* ciamap.c - This program reads a map database file in the CIA World Base
*      format and produces a netmap-format output file.
*
* Originally created by Brian Reid ([email protected])
* Modified March 1991 by Steve Putz ([email protected])
*****************************************************************************/

#include <stdio.h>
#include <math.h>
#include <ctype.h>
#include <strings.h>
#include <sys/file.h>
#include <sys/param.h>          /* for MAXPATHLEN */
#include "cbdmap.h"

#define BIT32   int             /* change these definitions to suit your */
#define BIT16   short           /* own machine                           */
#define BIT8    char
#define TRUE    1
#define FALSE   0
/*
* The data-compression scheme uses integer seconds to represent lat/long,
* and stores each stroke as a [dx,dy] from the previous point. If dy will
* fit into 8 bits and dx into 7 bits, then the entire [dx,dy] is stored in a
* 16-bit field with bit 0x4000 turned on as a flag. If either value is too
* large for that scheme, then both are stored as 32-bit values, with the
* 0x40000000 bit turned off (even in negative numbers) in the first of them.
*/

#define MAXSEG  20000           /* maximum strokes in a segment */
#define MAXSEGS 10000

#define abs(x)  (x<0 ? -(x) : x)
#define sign(x) ((x>=0) ? 1 : -1)

struct seghead  sb;

BIT16           segbuf[MAXSEG];

struct segdict  sd[MAXSEGS];
static long     acodes[MAXSEGS];
static long     acodeCounts[MAXSEGS];
static char     CodeTable[100];

int             MapDetail = 99;

int             rightShift = 0;
int             filterBits = 0;
int             paethFlag = 0;
int             inrec_bytes = 0;
int             debug = 0;

#define PUTSHORT(val, file) { putc((val)>>8, file); putc((val)&0xFF, file); }
#define PUTCOORD(seconds, file) PUTSHORT((seconds)/20, file)

/* ------------------------------------------------------------------------ */

main(argc, argv)
   int             argc;
   char          **argv;
{
   extern int      optind;
   extern char    *optarg;
   extern char    *strcpy();
   int             c, initdone, badopts;
   char            *outdir = NULL, *outfname = NULL;

   for (c = 0; c < 100; c++) CodeTable[c] = c;

   initdone = 0;
   badopts = 0;
   while ((c = getopt(argc, argv, "D:d:pn:s:f:t:T:O:o:m")) != EOF)
       switch (c) {
       case 'D':
           /* -D: debug. Argument is an integer debuging flags mask. */
           debug = strtol(optarg, NULL, 0);
           break;
       case 'd':
           /*
            * -d: detail. Argument is an integer giving map detail to
            * include.
            */
           sscanf(optarg, "%d", &MapDetail);
           break;
       case 'p':
           /* -p: Paeth format. Produce Paeth format map files.  */
           paethFlag = TRUE;
           break;
       case 'n':
           /*
            * -n: no newline chars in input. Argument is bytes/record.
            */
           sscanf(optarg, "%d", &inrec_bytes);
           break;
       case 's':
           /*
            * -s: Scale data. Argument is number of bits to right shift
            * coordinate data.
            */
           sscanf(optarg, "%d", &rightShift);
           break;
       case 'f':
           /* -f: Filter data. Argument is number of bits to smooth. */
           sscanf(optarg, "%d", &filterBits);
           break;
       case 't':
           /* -t: translate codes. Argument is feature code file name. */
           ReadCodes(optarg, CodeTable);
           break;
       case 'T':
           /* -T: translate codes. warn of features not in code file. */
           for (c = 0; c < 100; c++) CodeTable[c] = -1;
           ReadCodes(optarg, CodeTable);
           break;
       case 'O':
           /* -O: specify output directory */
           outdir = optarg;
           break;
       case 'o':
           /* -o: specify output file */
           outfname = optarg;
           break;
       case 'm':
           /* -m: Ascii map files follow. "-m f2 f2 f3" */
           for (; optind < argc && argv[optind][0] != '-'; optind++) {
               if (!initdone);
               initdone++;
               TranslateMap(argv[optind], outdir, outfname);
           }
           break;
       default:
           badopts++;
       }
   fflush(stdout);
   if (badopts) {
       exit(2);
   }
}


/* ------------------------------------------------------------------------ */

ReadCodes(name, codetable)
   char           *name;
   char        *codetable;
{
   FILE *df;
   int code1, code2, res;
   char desc[100];

   if (!(df = fopen(name, "r"))) {
       perror(name);
       exit(1);
   }
   while (!feof(df)) {
       *desc = '\0';
       res = fscanf(df, "%d %d %[^\n]\n", &code1, &code2, desc);
       if (res < 2) {
           fscanf(df, "%[^\n]\n", desc);
           if (debug & 0x1000)
               fprintf(stdout, "%s\n", desc);
       } else {
           if (debug & 0x2000)
               fprintf(stdout, "%d -> %d %s\n", code1, code2, desc);
           codetable[code1%100] = code2;
       }
   }

   fclose(df);

} /* ReadCodes */


/* ------------------------------------------------------------------------ */

TranslateMap(name, outdir, outfname)
   char           *name, *outdir, *outfname;
{
   FILE           *df;
   char            line[100], of[MAXPATHLEN], *bname, *ext,
                   lad,        /* 'N' or 'S' */
                   lod;        /* 'E' or 'W' */
   int             Ofile = -1, first, lsn, irnk, nop, ladg, lam, las,
                   lodg, lom, los,
                   ipn,        /* polygon number */
                   acode, acount,
                   big = 0, small = 0, medium = 0, skippedSegs = 0,
                   totalStrokes = 0, skippedStrokes = 0, lnum, nc,
                   i, j, strokecount, segmax, segsize, segcount, bytecount;
   BIT32           jlat,       /* integer latitude in seconds */
                   jlong,      /* integer longitude in seconds */
                   haveLat = 0, haveLong = 0, dx, dy;
   struct cbdhead  header;
   FILE           *paethIndex = NULL, *paethData = NULL;
   int             paethCount, paethTotal = 0;
   int             headerSize = CBD_HEADSIZE2;
   long            roundBit = 1 << (rightShift - 1);

   if (!(df = fopen(name, "r"))) {
       perror(name);
       exit(1);
   }
   bname = rindex(name, '/');
   if (bname) bname++;
   else bname = name;

   if (outdir) sprintf(of, "%s/", outdir);
   else *of = '\0';

   if (outfname) strcat(of, outfname);
   else strcat(of, bname);

   ext = index(of, '.');
   if (ext)
       *ext = '\0';
   else
       ext = of + strlen(of);

   if (paethFlag) {
       strcpy(ext, ".index");
       paethIndex = fopen(of, "w");
       strcpy(ext, ".map");
       paethData = fopen(of, "w");
       if (paethIndex == NULL || paethData == NULL) {
           perror(of);
           exit(1);
       }
   } else {
       if (!outfname) {
           if (rightShift) {
               sprintf(ext, "%+d", -rightShift);
               ext += strlen(ext);
           }
           if (filterBits) {
               sprintf(ext, "f%d", filterBits);
               ext += strlen(ext);
           }
           strcpy(ext, ".cbd");
       }
       Ofile = open(of, O_RDWR | O_CREAT | O_TRUNC, 0666);
       if (Ofile < 0) {
           perror(of);
           exit(1);
       }
   }

   fprintf(stdout, "Reading %s\nWriting %s\n", name, of);

   bzero(&header, sizeof(header));
   /* note that header.magic is zero until we are done */
   header.scale_shift = -rightShift;
   header.minlat = DEG90_SECS;
   header.maxlat = -DEG90_SECS;
   header.minlong = DEG180_SECS;
   header.maxlong = -DEG180_SECS;

   if (Ofile >= 0) {           /* reserve space for header */
       write(Ofile, &header, headerSize);
   }
   acount = 0;
   segcount = 0;
   segmax = 0;
   line[inrec_bytes] = '\0';

   /* The header line tells us  the serial number, rank, and path size */
   lnum = 0;
   for (;;) {
       if (inrec_bytes) {
           if (fread(line, inrec_bytes, 1, df) != 1) break;
       } else {
           if (fgets(line, 60, df) == NULL) break;
       }
       lnum++;
       for (nc = 0; nc < 20 && line[nc] != NULL; nc++)
          if (line[nc] == ' ') line[nc] = '0';

       if (nc < 18)
           continue;

       sscanf(line, "%7d%2d%6d%5d", &lsn, &irnk, &nop, &acode);
       if (debug & 0x100)
           fprintf(stdout, "%d: ID=%d, rank=%d, NP=%d, code=%d\n",
               segcount, lsn, irnk, nop, acode);
       acode = acode * 100 + irnk;
       if (CodeTable[irnk] > 32 || CodeTable[irnk] < 1) {
           fprintf(stdout, "WARNING: file %s, seg %d: ID=%d, rank=%d, NP=%d, code=%d -> %d\n",
               name, segcount, lsn, irnk, nop, acode, CodeTable[irnk]);
           irnk = 32;
       } else {
           irnk = CodeTable[irnk];
       }

       if (irnk > MapDetail) {
           for (i = 1; i <= nop; i++) {
               if (inrec_bytes) {
                   if (fread(line, inrec_bytes, 1, df) != 1) break;
               } else {
                   if (fgets(line, 60, df) == NULL) break;
               }
               lnum++;
           }
       } else {
           sb.id = lsn;
           first = FALSE;
           if (segcount > MAXSEGS) {
               fprintf(stdout, "Segment count overflow.\n");
               close(Ofile);
               fclose(df);
               return;
           }
           if (Ofile >= 0)
               sd[segcount].absaddr = tell(Ofile);
           sd[segcount].segid = lsn;
           sd[segcount].rank = irnk;

           sd[segcount].minlat = DEG90_SECS;
           sd[segcount].maxlat = -DEG90_SECS;
           sd[segcount].minlong = DEG180_SECS;
           sd[segcount].maxlong = -DEG180_SECS;

           {   int found = 0;
               for (i = 0; i < acount; i++) {
                   if (acodes[i] == acode) {
                       acodeCounts[i]++;
                       found = 1;
                       break;
                   }
               }
               if (!found) {
                   acodeCounts[acount] = 1;
                   acodes[acount++] = acode;
                   if (debug & 0x800)
                       fprintf(stdout, "code %d\n", acode);
               }
           }

           bytecount = 0;
           segsize = 0;
           paethCount = 0;
           for (i = 1; i <= nop; i++) {
               if (inrec_bytes) {
                   if (fread(line, inrec_bytes, 1, df) != 1) break;
               } else {
                   if (fgets(line, 60, df) == NULL) break;
               }
               lnum++;
               for (j = 0; j <= 19; j++) {
                   if (line[j] == ' ')
                       line[j] = '0';
               }
#define DIG(x) ((int)(x)-48)
               ladg = 10 * DIG(line[0]) + DIG(line[1]);
               lam = 10 * DIG(line[2]) + DIG(line[3]);
               las = 10 * DIG(line[4]) + DIG(line[5]);
               lad = line[6];
               lodg = 100 * DIG(line[7]) + 10 * DIG(line[8]) + DIG(line[9]);
               lom = 10 * DIG(line[10]) + DIG(line[11]);
               los = 10 * DIG(line[12]) + DIG(line[13]);
               lod = line[14];
               ipn = 1000 * DIG(line[16]) + 100 * DIG(line[17]) + 10 *
                       DIG(line[18]) + DIG(line[19]);

               if (debug & 0x200)
                   fprintf(stdout, "%d: %02d %02d %02d %c %03d %02d %02d %c %05d\n",
                       segcount, ladg, lam, las, lad, lodg, lom, los, lod, ipn);

               if (ipn != i) {
                   fprintf(stdout, "Data error at line %d, sequence %d, segment %d: expected %d but found: %d\n%s\n",
                       lnum, lsn, segcount, i, ipn, line);
                   if (ipn > 0 && ipn < nop)
                       i = ipn;
               }
               jlat = 3600 * ladg + 60 * lam + las;
               if (lad == 'S')
                   jlat = -jlat;
               jlong = 3600 * lodg + 60 * lom + los;
               if (lod == 'W')
                   jlong = -jlong;
               if (debug & 0x04)
                   fprintf(stdout, "(%.4f %.4f) = (%d %d) [%x %x]",
                    jlong / 3600.0, jlat / 3600.0, jlong, jlat, jlong, jlat);
               if (rightShift > 0) {
                   if (jlong & roundBit) {
                       jlong = (jlong >> rightShift) + 1;
                       if (debug & 0x20) fprintf(stdout, " x%+1 ");
                   } else {
                       jlong >>= rightShift;
                   }
                   if (jlat & roundBit) {
                       jlat = (jlat >> rightShift) + 1;
                       if (debug & 0x20) fprintf(stdout, " y%+1 ");
                   } else {
                       jlat >>= rightShift;
                   }
                   if (debug & 0x10)
                       fprintf(stdout, "-> (%d %d) [%x %x]", jlong, jlat, jlong, jlat);
               }
               if (debug & 0x04)
                   fprintf(stdout, "\n");
               if (sd[segcount].maxlat < jlat)
                   sd[segcount].maxlat = jlat;
               if (sd[segcount].minlat > jlat)
                   sd[segcount].minlat = jlat;
               if (sd[segcount].maxlong < jlong)
                   sd[segcount].maxlong = jlong;
               if (sd[segcount].minlong > jlong)
                   sd[segcount].minlong = jlong;
               if (!first) {
                   first = TRUE;
                   sb.orgx = jlong;
                   sb.orgy = jlat;
                   strokecount = 0;
               } else {
                   dx = jlong - haveLong;
                   dy = jlat - haveLat;
                   if (dx == 0 && dy == 0 ||
                               i < nop && abs(dx) + abs(dy) <= filterBits) {
                       skippedStrokes++;
                       if (debug & 0x02)
                           fprintf(stdout, "skipped: (%d %d)\n", dx, dy);
                       continue;       /* don't set haveLat, haveLong */
                   } else {
                       totalStrokes++;
                       strokecount++;
                       if (abs(dx) > MAX8x || abs(dy) > MAX8y) {
                           /*
                            * Encode this transition as a pair of 32-bit
                            * values.
                            */
                           big++;
                           if (abs(dx) <= 0x3FFF && abs(dy) <= 0x7FFF)
                               medium++;
                           bytecount += 8;
                           if (segsize + 4 > MAXSEG) {
                               fprintf(stdout, "segsize overflow.\n");
                               close(Ofile);
                               fclose(df);
                               return;
                           }
                           segbuf[segsize++] = (dx >> 16) & ~SHORTFLAG;
                           segbuf[segsize++] = dx & 0xFFFF;
                           segbuf[segsize++] = (dy >> 16);
                           segbuf[segsize++] = dy & 0xFFFF;
                           if (debug & 0x02)
                               fprintf(stdout, "%4d: long (%d %d): %04x %04x %04x %04x\n", strokecount, dx, dy, segbuf[segsize - 4] & 0xFFFF, segbuf[segsize - 3] & 0xFFFF, segbuf[segsize - 2] & 0xFFFF, segbuf[segsize - 1] & 0xFFFF);
                       } else {
                           /*
                            * Encode this transition as a pair of 8-bit
                            * values.
                            */
                           small++;
                           bytecount += 2;
                           if (segsize + 1 > MAXSEG) {
                               fprintf(stdout, "segsize overflow.\n");
                               close(Ofile);
                               fclose(df);
                               return;
                           }
                           segbuf[segsize++] =
                               SHORTFLAG |
                               (dx << 8) |
                               (dy & 0xFF);
                           if (debug & 0x02)
                               fprintf(stdout, "%4d: short (%d %d): %04x\n", strokecount, dx, dy, segbuf[segsize - 1] & 0xFFFF);
                       }       /* short encoding */
                       if (paethFlag) {
                           PUTCOORD(jlat, paethData);
                           PUTCOORD(jlong, paethData);
                           paethCount++;
                       }
                   }           /* dx,dy != 0 */
               }               /* not first */
               haveLat = jlat;
               haveLong = jlong;
           }

           if (strokecount == 0) {
               skippedSegs++;  /* reuse same segment */
           } else if (Ofile >= 0) {
               sd[segcount].nbytes = bytecount;
               sb.nstrokes = strokecount;
               write(Ofile, &sb, sizeof sb);
               write(Ofile, segbuf, bytecount);
               if (debug & 0x01)
                   fprintf(stdout, "seg %d: (%.4f %.4f)=(%d %d) %d strokes %d bytes, code %03d %04d\n", sb.id, (sb.orgx << rightShift) / 3600.0, (sb.orgy << rightShift) / 3600.0, sb.orgx, sb.orgy, sb.nstrokes, bytecount, acode/10000, acode % 10000);
               if (debug & 0x0400)
                   fprintf(stdout, "seg NSEW = (%.4f %.4f %.4f %.4f)\n",
                           (sd[segcount].maxlat << rightShift) / 3600.0,
                           (sd[segcount].minlat << rightShift) / 3600.0,
                           (sd[segcount].maxlong << rightShift) / 3600.0,
                           (sd[segcount].minlong << rightShift) / 3600.0);
               header.features |= (1 << (sd[segcount].rank - 1));
               if (header.maxlat < sd[segcount].maxlat)
                   header.maxlat = sd[segcount].maxlat;
               if (header.minlat > sd[segcount].minlat)
                   header.minlat = sd[segcount].maxlat;
               if (header.maxlong < sd[segcount].maxlong)
                   header.maxlong = sd[segcount].maxlong;
               if (header.minlong > sd[segcount].minlong)
                   header.minlong = sd[segcount].minlong;
               segcount++;
           } else if (paethFlag) {
               PUTCOORD(sd[segcount].maxlat, paethIndex);
               PUTCOORD(sd[segcount].minlat, paethIndex);
               PUTCOORD(sd[segcount].maxlong, paethIndex);
               PUTCOORD(sd[segcount].minlong, paethIndex);
               PUTSHORT(paethCount, paethIndex);
               paethTotal += paethCount;
           }
       }
       /* end of each segment */

       if (segsize > segmax)
           segmax = segsize;
   }
   /* end of file */
   header.magic = headerSize == CBD_HEADSIZE2 ? CBD_MAGIC2 : CBD_MAGIC;
   header.dictaddr = tell(Ofile);
   header.segcount = segcount;
   header.segsize = segcount * sizeof(struct segdict);
   header.segmax = segmax;

   fprintf(stdout,
           "Segdict has %d entries (%d skipped), %d bytes, starts at %d.\n\
       %d strokes (%d skipped)\n\
       Largest segment has %d entries (%d bytes)\n\
       %d small entries, %d big (%.2f%% of space), %d medium\n",
           segcount, skippedSegs, header.segsize, header.dictaddr,
           totalStrokes, skippedStrokes,
           segmax, 2 * segmax,
           small, big, (double) 100 * big * 4 / (big * 4 + small), medium);
   {   int i;
       fprintf(stdout, "features:");
       for (i = 1; i <= 32; i++)
           if (header.features & (1<<(i-1))) fprintf(stdout, " %d", i);
       fprintf(stdout, "\n");
       fprintf(stdout, "%d codes:\n", acount);
       for (i = 0; i < acount; i++)
           fprintf(stdout, "code %07d %5d\n", acodes[i], acodeCounts[i]);
   }
   if (paethFlag) {
       fprintf(stdout, "\t%d paeth points\n", paethTotal);
       fclose(paethData);
       fclose(paethIndex);
   } else {
       write(Ofile, sd, (int) header.segsize);
       lseek(Ofile, 0L, L_SET);
       write(Ofile, &header, headerSize);
       close(Ofile);
   }
   fclose(df);
}