Imported Upstream version 1.1.1
[debian/splat] / splat.cpp
1 /****************************************************************************
2 *      SPLAT: An RF Signal Propagation Loss and Terrain Analysis Tool       *
3 *                         Last update: 31-Mar-2006                          *
4 *****************************************************************************
5 *            Project started in 1997 by John A. Magliacane, KD2BD           *
6 *****************************************************************************
7 *                                                                           *
8 *     Extensively modified by J. D. McDonald in Jan. 2004 to include        *
9 *    the Longley-Rice propagation model using C++ code from NTIA/ITS.       *
10 *                                                                           *
11 *              See: http://flattop.its.bldrdoc.gov/itm.html                 *
12 *                                                                           *
13 *****************************************************************************
14 *                                                                           *
15 * This program is free software; you can redistribute it and/or modify it   *
16 * under the terms of the GNU General Public License as published by the     *
17 * Free Software Foundation; either version 2 of the License or any later    *
18 * version.                                                                  *
19 *                                                                           *
20 * This program is distributed in the hope that it will useful, but WITHOUT  *
21 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or     *
22 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License     *
23 * for more details.                                                         *
24 *                                                                           *
25 *****************************************************************************
26  g++ -Wall -O3 -s -lm -lbz2 -fomit-frame-pointer itm.cpp splat.cpp -o splat 
27 *****************************************************************************/
28
29 #include <stdio.h>
30 #include <math.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <bzlib.h>
35 #include <unistd.h>
36 #include "fontdata.h"
37 #include "smallfont.h"
38
39 #define GAMMA 2.5
40 #define MAXSLOTS 9
41 #define BZBUFFER 65536
42
43 #if MAXSLOTS==4
44 #define ARRAYSIZE 4950
45 #endif
46
47 #if MAXSLOTS==9
48 #define ARRAYSIZE 10870
49 #endif
50
51 #if MAXSLOTS==16
52 #define ARRAYSIZE 19240
53 #endif
54
55 #if MAXSLOTS==25
56 #define ARRAYSIZE 30025
57 #endif
58
59 char    string[255], sdf_path[255], opened=0, *splat_version={"1.1.1"};
60
61 double  TWOPI=6.283185307179586, HALFPI=1.570796326794896,
62         PI=3.141592653589793, deg2rad=1.74532925199e-02,
63         EARTHRADIUS=20902230.97, METERS_PER_MILE=1609.344,
64         METERS_PER_FOOT=0.3048, earthradius, max_range=0.0;
65
66 int     min_north=90, max_north=-90, min_west=360, max_west=-1,
67         max_elevation=-32768, min_elevation=32768, bzerror, maxdB=230;
68
69 struct site { double lat;
70               double lon;
71               double alt;
72               char name[50];
73             } site;
74
75 struct  path { float lat[ARRAYSIZE];
76                float lon[ARRAYSIZE];
77                float elevation[ARRAYSIZE];
78                float distance[ARRAYSIZE];
79                int length;
80              } path;
81
82 struct dem { int min_north;
83              int max_north;
84              int min_west;
85              int max_west;
86              int max_el;
87              int min_el;
88              short data[1200][1200];
89              unsigned char mask[1200][1200];
90            } dem[MAXSLOTS];
91
92 struct LR { double eps_dielect; 
93             double sgm_conductivity; 
94             double eno_ns_surfref;
95             double frq_mhz; 
96             double conf; 
97             double rel;
98             int radio_climate;  
99             int pol;
100           } LR;
101
102 double elev_l[ARRAYSIZE+10];
103
104 void point_to_point(double elev[], double tht_m, double rht_m,
105           double eps_dielect, double sgm_conductivity, double eno_ns_surfref,
106           double frq_mhz, int radio_climate, int pol, double conf,
107           double rel, double &dbloss, char *strmode, int &errnum);
108
109 double arccos(double x, double y)
110 {
111         /* This function implements the arc cosine function,
112            returning a value between 0 and TWOPI. */
113
114         double result=0.0;
115
116         if (y>0.0)
117                 result=acos(x/y);
118
119         if (y<0.0)
120                 result=PI+acos(x/y);
121
122         return result;
123 }
124
125 int ReduceAngle(double angle)
126 {
127         /* This function normalizes the argument to
128            an integer angle between 0 and 180 degrees */
129
130         double temp;
131
132         temp=acos(cos(angle*deg2rad));
133
134         return (int)rint(temp/deg2rad);
135 }
136
137 char *dec2dms(double decimal)
138 {
139         /* Converts decimal degrees to degrees, minutes, seconds,
140            (DMS) and returns the result as a character string. */
141  
142         int degrees, minutes, seconds;
143         double a, b, c, d;
144
145         if (decimal<0.0)
146                 decimal=-decimal;
147
148         a=floor(decimal);
149         b=60.0*(decimal-a);
150         c=floor(b);
151         d=60.0*(b-c);
152
153         degrees=(int)a;
154         minutes=(int)c;
155         seconds=(int)d;
156
157         if (seconds<0)
158                 seconds=0;
159
160         if (seconds>59)
161                 seconds=59;
162
163         string[0]=0;
164         sprintf(string,"%d%c %d\' %d\"", degrees, 176, minutes, seconds);
165         return (string);
166 }
167
168 int OrMask(double lat, double lon, int value)
169 {
170         /* Lines, text, markings, and coverage areas are stored in a
171            mask that is combined with topology data when topographic
172            maps are generated by SPLAT!.  This function sets bits in
173            the mask based on the latitude and longitude of the area
174            pointed to. */
175
176         int x, y, indx, minlat, minlon;
177         char found;
178
179         minlat=(int)floor(lat);
180         minlon=(int)floor(lon);
181
182         for (indx=0, found=0; indx<MAXSLOTS && found==0;)
183                 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
184                         found=1;
185                 else
186                         indx++;
187
188         if (found)
189         {
190                 x=(int)(1199.0*(lat-floor(lat)));
191                 y=(int)(1199.0*(lon-floor(lon)));
192
193                 dem[indx].mask[x][y]|=value;
194
195                 return (dem[indx].mask[x][y]);
196         }
197
198         else
199                 return -1;
200 }
201
202 int GetMask(double lat, double lon)
203 {
204         /* This function returns the mask bits based on the latitude
205            and longitude given. */
206
207         return (OrMask(lat,lon,0));
208 }
209
210 double GetElevation(struct site location)
211 {
212         /* This function returns the elevation (in feet) of any location
213            represented by the digital elevation model data in memory.
214            Function returns -5000.0 for locations not found in memory. */
215
216         char found;
217         int x, y, indx, minlat, minlon;
218         double elevation;
219
220         elevation=-5000.0;
221
222         minlat=(int)floor(location.lat);
223         minlon=(int)floor(location.lon);
224
225         x=(int)(1199.0*(location.lat-floor(location.lat)));
226         y=(int)(1199.0*(location.lon-floor(location.lon)));
227
228         for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
229         {
230                 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
231                 {
232
233                         elevation=3.28084*dem[indx].data[x][y];
234                         found=1;
235                 }
236         }
237         
238         return elevation;
239 }
240
241 double Distance(struct site site1, struct site site2)
242 {
243         /* This function returns the great circle distance
244            in miles between any two site locations. */
245
246         double lat1, lon1, lat2, lon2, distance;
247
248         lat1=site1.lat*deg2rad;
249         lon1=site1.lon*deg2rad;
250         lat2=site2.lat*deg2rad;
251         lon2=site2.lon*deg2rad;
252
253         distance=3959.0*acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos((lon1)-(lon2)));
254
255         return distance;
256 }
257
258 double Azimuth(struct site source, struct site destination)
259 {
260         /* This function returns the azimuth (in degrees) to the
261            destination as seen from the location of the source. */
262
263         double dest_lat, dest_lon, src_lat, src_lon,
264                beta, azimuth, diff, num, den, fraction;
265
266         dest_lat=destination.lat*deg2rad;
267         dest_lon=destination.lon*deg2rad;
268
269         src_lat=source.lat*deg2rad;
270         src_lon=source.lon*deg2rad;
271                 
272         /* Calculate Surface Distance */
273
274         beta=acos(sin(src_lat)*sin(dest_lat)+cos(src_lat)*cos(dest_lat)*cos(src_lon-dest_lon));
275
276         /* Calculate Azimuth */
277
278         num=sin(dest_lat)-(sin(src_lat)*cos(beta));
279         den=cos(src_lat)*sin(beta);
280         fraction=num/den;
281
282         /* Trap potential problems in acos() due to rounding */
283
284         if (fraction>=1.0)
285                 fraction=1.0;
286
287         if (fraction<=-1.0)
288                 fraction=-1.0;
289
290         /* Calculate azimuth */
291
292         azimuth=acos(fraction);
293
294         /* Reference it to True North */
295
296         diff=dest_lon-src_lon;
297
298         if (diff<=-PI)
299                 diff+=TWOPI;
300
301         if (diff>=PI)
302                 diff-=TWOPI;
303
304         if (diff>0.0)
305                 azimuth=TWOPI-azimuth;
306
307         return (azimuth/deg2rad);               
308 }
309
310 double ElevationAngle(struct site local, struct site remote)
311 {
312         /* This function returns the angle of elevation (in degrees)
313            of the remote location as seen from the local site.
314            A positive result represents an angle of elevation (uptilt),
315            while a negative result represents an angle of depression
316            (downtilt), as referenced to a normal to the center of
317            the earth. */
318            
319         register double a, b, dx;
320
321         a=GetElevation(remote)+remote.alt+earthradius;
322         b=GetElevation(local)+local.alt+earthradius;
323
324         dx=5280.0*Distance(local,remote);
325
326         /* Apply the Law of Cosines */
327
328         return ((180.0*(acos(((b*b)+(dx*dx)-(a*a))/(2.0*b*dx)))/PI)-90.0);
329 }
330
331 void ReadPath(struct site source, struct site destination)
332 {
333         /* This function generates a sequence of latitude and
334            longitude positions between a source location and
335            a destination along a great circle path, and stores
336            elevation and distance information for points along
337            that path in the "path" structure for later use. */
338
339         int c;
340         double azimuth, distance, lat1, lon1, beta,
341                den, num, lat2, lon2, total_distance;
342         struct site tempsite;
343
344         lat1=source.lat*deg2rad;
345         lon1=source.lon*deg2rad;
346         azimuth=Azimuth(source,destination)*deg2rad;
347         total_distance=Distance(source,destination);
348
349         for (distance=0, c=0; distance<=total_distance; distance+=0.04)
350         {
351                 beta=distance/3959.0;
352                 lat2=asin(sin(lat1)*cos(beta)+cos(azimuth)*sin(beta)*cos(lat1));
353                 num=cos(beta)-(sin(lat1)*sin(lat2));
354                 den=cos(lat1)*cos(lat2);
355
356                 if (azimuth==0.0 && (beta>HALFPI-lat1))
357                         lon2=lon1+PI;
358
359                 else if (azimuth==HALFPI && (beta>HALFPI+lat1))
360                         lon2=lon1+PI;
361
362                 else if (fabs(num/den)>1.0)
363                         lon2=lon1;
364
365                 else
366                 {
367                         if ((PI-azimuth)>=0.0)
368                                 lon2=lon1-arccos(num,den);
369                         else
370                                 lon2=lon1+arccos(num,den);
371                 }
372         
373                 while (lon2<0.0)
374                         lon2+=TWOPI;
375
376                 while (lon2>TWOPI)
377                         lon2-=TWOPI;
378  
379                 lat2=lat2/deg2rad;
380                 lon2=lon2/deg2rad;
381
382                 if (c<ARRAYSIZE)
383                 {
384                         path.lat[c]=lat2;
385                         path.lon[c]=lon2;
386                         tempsite.lat=lat2;
387                         tempsite.lon=lon2;
388                         path.elevation[c]=GetElevation(tempsite);
389                         path.distance[c]=distance;
390                         c++;
391                 }
392         }
393
394         /* Make sure exact destination point is recorded at path.length-1 */
395
396         if (c<ARRAYSIZE)
397         {
398                 path.lat[c]=destination.lat;
399                 path.lon[c]=destination.lon;
400                 path.elevation[c]=GetElevation(destination);
401                 path.distance[c]=total_distance;
402                 c++;
403         }
404
405         if (c<ARRAYSIZE)
406                 path.length=c;
407         else
408                 path.length=ARRAYSIZE-1;
409 }
410
411 double AverageTerrain(struct site source, double azimuthx, double start_distance, double end_distance)
412 {
413         /* This function returns the average terrain calculated in
414            the direction of "azimuth" (degrees) between "start_distance"
415            and "end_distance" (miles) from the source location.  If
416            the terrain is all water (non-critical error), -5000.0 is
417            returned.  If not enough SDF data has been loaded into
418            memory to complete the survey (critical error), then
419            -9999.0 is returned. */
420  
421         int c, samples, endpoint;
422         double beta, lat1, lon1, lat2, lon2, num, den, azimuth, terrain=0.0;
423         struct site destination;
424
425         lat1=source.lat*deg2rad;
426         lon1=source.lon*deg2rad;
427
428         /* Generate a path of elevations between the source
429            location and the remote location provided. */
430
431         beta=end_distance/3959.0;
432
433         azimuth=deg2rad*azimuthx;
434
435         lat2=asin(sin(lat1)*cos(beta)+cos(azimuth)*sin(beta)*cos(lat1));
436         num=cos(beta)-(sin(lat1)*sin(lat2));
437         den=cos(lat1)*cos(lat2);
438
439         if (azimuth==0.0 && (beta>HALFPI-lat1))
440                 lon2=lon1+PI;
441
442         else if (azimuth==HALFPI && (beta>HALFPI+lat1))
443                 lon2=lon1+PI;
444
445         else if (fabs(num/den)>1.0)
446                 lon2=lon1;
447
448         else
449         {
450                 if ((PI-azimuth)>=0.0)
451                         lon2=lon1-arccos(num,den);
452                 else
453                         lon2=lon1+arccos(num,den);
454         }
455         
456         while (lon2<0.0)
457                 lon2+=TWOPI;
458
459         while (lon2>TWOPI)
460                 lon2-=TWOPI;
461  
462         lat2=lat2/deg2rad;
463         lon2=lon2/deg2rad;
464
465         destination.lat=lat2;
466         destination.lon=lon2;
467
468         /* If SDF data is missing for the endpoint of
469            the radial, then the average terrain cannot
470            be accurately calculated.  Return -9999.0 */
471
472         if (GetElevation(destination)<-4999.0)
473                 return (-9999.0);
474         else
475         {
476                 ReadPath(source,destination);
477
478                 endpoint=path.length;
479
480                 /* Shrink the length of the radial if the
481                    outermost portion is not over U.S. land. */
482
483                 for (c=endpoint-1; c>=0 && path.elevation[c]==0.0; c--);
484
485                 endpoint=c+1;
486
487                 for (c=0, samples=0; c<endpoint; c++)
488                 {
489                         if (path.distance[c]>=start_distance)
490                         {
491                                 terrain+=path.elevation[c];
492                                 samples++;
493                         }
494                 }
495
496                 if (samples==0)
497                         terrain=-5000.0;  /* No land */
498                 else
499                         terrain=(terrain/(double)samples);
500
501                 return terrain;
502         }
503 }
504
505 double haat(struct site antenna)
506 {
507         /* This function returns the antenna's Height Above Average
508            Terrain (HAAT) based on FCC Part 73.313(d).  If a critical
509            error occurs, such as a lack of SDF data to complete the
510            survey, -5000.0 is returned. */
511
512         int azi, c;
513         char error=0;
514         double terrain, avg_terrain, haat, sum=0.0;
515
516         /* Calculate the average terrain between 2 and 10 miles
517            from the antenna site at azimuths of 0, 45, 90, 135,
518            180, 225, 270, and 315 degrees. */
519
520         for (c=0, azi=0; azi<=315 && error==0; azi+=45)
521         {
522                 terrain=AverageTerrain(antenna, (double)azi, 2.0, 10.0);
523
524                 if (terrain<-9998.0)  /* SDF data is missing */
525                         error=1;
526
527                 if (terrain>-4999.0)  /* It's land, not water */
528                 {
529                         sum+=terrain;  /* Sum of averages */
530                         c++;
531                 }
532         }
533
534         if (error)
535                 return -5000.0;
536         else
537         {
538                 avg_terrain=(sum/(double)c);
539                 haat=(antenna.alt+GetElevation(antenna))-avg_terrain;
540                 return haat;
541         }
542 }
543
544 float LonDiff(float lon1, float lon2)
545 {
546         /* This function returns the short path longitudinal
547            difference between longitude1 and longitude2 
548            as an angle between -180.0 and +180.0 degrees.
549            If lon1 is west of lon2, the result is positive.
550            If lon1 is east of lon2, the result is negative. */
551
552         float diff;
553
554         diff=lon1-lon2;
555
556         if (diff<=-180.0)
557                 diff+=360.0;
558
559         if (diff>=180.0)
560                 diff-=360.0;
561
562         return diff;
563 }
564
565 void PlaceMarker(struct site location)
566 {
567         /* This function places text and marker data in the mask array
568            for illustration on topographic maps generated by SPLAT!.
569            By default, SPLAT! centers text information BELOW the marker,
570            but may move it above, to the left, or to the right of the
571            marker depending on how much room is available on the map,
572            or depending on whether the area is already occupied by
573            another marker or label.  If no room or clear space is
574            available on the map to place the marker and its associated
575            text, then the marker and text are not written to the map. */
576  
577         int     a, b, c, byte;
578         char    ok2print, occupied;
579         double  x, y, lat, lon, textx=0.0, texty=0.0, xmin, xmax,
580                 ymin, ymax, p1, p3, p6, p8, p12, p16, p24, label_length;
581
582         xmin=min_north;
583         xmax=max_north;
584         ymin=min_west;
585         ymax=max_west;
586         lat=location.lat;
587         lon=location.lon;
588
589
590         if (lat<xmax && lat>xmin && (LonDiff(lon,ymax)<0.0) && (LonDiff(lon,ymin)>0.0))
591         {
592                 p1=1.0/1200.0;
593                 p3=3.0/1200.0;
594                 p6=6.0/1200.0;
595                 p8=8.0/1200.0;
596                 p12=12.0/1200.0;
597                 p16=16.0/1200.0;
598                 p24=24.0/1200.0;
599                 ok2print=0;
600                 occupied=0;
601
602                 /* Is Marker Position Clear Of Text Or Other Markers? */
603
604                 for (x=lat-p3; (x<=xmax && x>=xmin && x<=lat+p3); x+=p1)
605                         for (y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1)
606                                 occupied|=(GetMask(x,y)&2);
607
608                 if (occupied==0)
609                 {
610                         /* Determine Where Text Can Be Positioned */
611
612                         /* label_length=length in pixels.
613                            Each character is 8 pixels wide. */
614
615                         label_length=p1*(double)(strlen(location.name)<<3);
616
617                         if ((LonDiff(lon+label_length,ymax)<=0.0) && (LonDiff(lon-label_length,ymin)>=0.0))
618                         {
619                                 /* Default: Centered Text */
620
621                                 texty=lon+label_length/2.0;
622
623                                 if ((lat-p8)>=p16)
624                                 {
625                                         /* Position Text Below The Marker */
626
627                                         textx=lat-p8;
628
629                                         x=textx;
630                                         y=texty;
631         
632                                         /* Is This Position Clear Of
633                                            Text Or Other Markers? */
634
635                                         for (a=0, occupied=0; a<16; a++)
636                                         {
637                                                 for (b=0; b<(int)strlen(location.name); b++)
638                                                         for (c=0; c<8; c++, y-=p1)
639                                                                 occupied|=(GetMask(x,y)&2);
640                                                 x-=p1;
641                                                 y=texty;
642                                         }
643
644                                         x=textx;
645                                         y=texty;
646
647                                         if (occupied==0)
648                                                 ok2print=1;
649                                 }
650
651                                 else
652                                 {
653                                         /* Position Text Above The Marker */
654
655                                         textx=lat+p24;
656
657                                         x=textx;
658                                         y=texty;
659         
660                                         /* Is This Position Clear Of
661                                            Text Or Other Markers? */
662
663                                         for (a=0, occupied=0; a<16; a++)
664                                         {
665                                                 for (b=0; b<(int)strlen(location.name); b++)
666                                                         for (c=0; c<8; c++, y-=p1)
667                                                                 occupied|=(GetMask(x,y)&2);
668                                                 x-=p1;
669                                                 y=texty;
670                                         }
671
672                                         x=textx;
673                                         y=texty;
674
675                                         if (occupied==0)
676                                                 ok2print=1;
677                                 }
678                         }
679
680                         if (ok2print==0)
681                         {
682                                 if (LonDiff(lon-label_length,ymin)>=0.0)
683                                 {
684                                         /* Position Text To The
685                                            Right Of The Marker */
686
687                                         textx=lat+p6;
688                                         texty=lon-p12;
689
690                                         x=textx;
691                                         y=texty;
692         
693                                         /* Is This Position Clear Of
694                                            Text Or Other Markers? */
695
696                                         for (a=0, occupied=0; a<16; a++)
697                                         {
698                                                 for (b=0; b<(int)strlen(location.name); b++)
699                                                         for (c=0; c<8; c++, y-=p1)
700                                                                 occupied|=(GetMask(x,y)&2);
701                                                 x-=p1;
702                                                 y=texty;
703                                         }
704
705                                         x=textx;
706                                         y=texty;
707
708                                         if (occupied==0)
709                                                 ok2print=1;
710                                 }
711
712                                 else
713                                 {
714                                         /* Position Text To The
715                                            Left Of The Marker */
716
717                                         textx=lat+p6;
718                                         texty=lon+p8+(label_length);
719
720                                         x=textx;
721                                         y=texty;
722         
723                                         /* Is This Position Clear Of
724                                            Text Or Other Markers? */
725
726                                         for (a=0, occupied=0; a<16; a++)
727                                         {
728                                                 for (b=0; b<(int)strlen(location.name); b++)
729                                                         for (c=0; c<8; c++, y-=p1)
730                                                                 occupied|=(GetMask(x,y)&2);
731                                                 x-=p1;
732                                                 y=texty;
733                                         }
734
735                                         x=textx;
736                                         y=texty;
737
738                                         if (occupied==0)
739                                                 ok2print=1;
740                                 }
741                         }
742
743                         /* textx and texty contain the latitude and longitude
744                            coordinates that describe the placement of the text
745                            on the map. */
746         
747                         if (ok2print)
748                         {
749                                 /* Draw Text */
750
751                                 x=textx;
752                                 y=texty;
753
754                                 for (a=0; a<16 && ok2print; a++)
755                                 {
756                                         for (b=0; b<(int)strlen(location.name); b++)
757                                         {
758                                                 byte=fontdata[16*(location.name[b])+a];
759
760                                                 for (c=128; c>0; c=c>>1, y-=p1)
761                                                         if (byte&c)
762                                                                 OrMask(x,y,2);
763                                         }
764
765                                         x-=p1;
766                                         y=texty;
767                                 }
768         
769                                 /* Draw Square Marker Centered
770                                    On Location Specified */
771         
772                                 for (x=lat-p3; (x<=xmax && x>=xmin && x<=lat+p3); x+=p1)
773                                         for (y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1)
774                                                 OrMask(x,y,2);
775                         }
776                 }
777         }
778 }
779
780 double ReadBearing(char *input)
781 {
782         /* This function takes numeric input in the form of a character
783            string, and returns an equivalent bearing in degrees as a
784            decimal number (double).  The input may either be expressed
785            in decimal format (40.139722) or degree, minute, second
786            format (40 08 23).  This function also safely handles
787            extra spaces found either leading, trailing, or
788            embedded within the numbers expressed in the
789            input string.  Decimal seconds are permitted. */
790  
791         double seconds, bearing=0.0;
792         char string[20];
793         int a, b, length, degrees, minutes;
794
795         /* Copy "input" to "string", and ignore any extra
796            spaces that might be present in the process. */
797
798         string[0]=0;
799         length=strlen(input);
800
801         for (a=0, b=0; a<length && a<18; a++)
802         {
803                 if ((input[a]!=32 && input[a]!='\n') || (input[a]==32 && input[a+1]!=32 && b!=0))
804                 {
805                         string[b]=input[a];
806                         b++;
807                 }        
808         }
809
810         string[b]=0;
811
812         /* Count number of spaces in the clean string. */
813
814         length=strlen(string);
815
816         for (a=0, b=0; a<length; a++)
817                 if (string[a]==32)
818                         b++;
819
820         if (b==0)  /* Decimal Format (40.139722) */
821                 sscanf(string,"%lf",&bearing);
822
823         if (b==2)  /* Degree, Minute, Second Format (40 08 23) */
824         {
825                 sscanf(string,"%d %d %lf",&degrees, &minutes, &seconds);
826                 bearing=(double)degrees+((double)minutes/60)+(seconds/3600);
827         }
828
829         /* Anything else returns a 0.0 */
830
831         if (bearing>360.0 || bearing<-90.0)
832                 bearing=0.0;
833
834         return bearing;
835 }
836
837 struct site LoadQTH(char *filename)
838 {
839         /* This function reads SPLAT! .qth (site location) files.
840            The latitude and longitude may be expressed either in
841            decimal degrees, or in degree, minute, second format.
842            Antenna height is assumed to be expressed in feet above
843            ground level (AGL), unless followed by the letter 'M',
844            or 'm', or by the word "meters" or "Meters", in which
845            case meters is assumed, and is handled accordingly. */
846
847         int x;
848         char string[50], qthfile[255];
849         struct site tempsite;
850         FILE *fd=NULL;
851
852         for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
853                 qthfile[x]=filename[x];
854
855         qthfile[x]='.';
856         qthfile[x+1]='q';
857         qthfile[x+2]='t';
858         qthfile[x+3]='h';
859         qthfile[x+4]=0;
860
861         tempsite.lat=91.0;
862         tempsite.lon=361.0;
863         tempsite.alt=0.0;
864         tempsite.name[0]=0;
865
866         fd=fopen(qthfile,"r");
867
868         if (fd!=NULL)
869         {
870                 /* Site Name */
871                 fgets(string,49,fd);
872
873                 /* Strip <CR> and/or <LF> from end of site name */
874
875                 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; tempsite.name[x]=string[x], x++);
876
877                 tempsite.name[x]=0;
878
879                 /* Site Latitude */
880                 fgets(string,49,fd);
881                 tempsite.lat=ReadBearing(string);
882
883                 /* Site Longitude */
884                 fgets(string,49,fd);
885                 tempsite.lon=ReadBearing(string);
886
887                 /* Antenna Height */
888                 fgets(string,49,fd);
889                 fclose(fd);
890
891                 /* Remove <CR> and/or <LF> from antenna height string */
892                 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; x++);
893
894                 string[x]=0;
895
896                 /* Antenna height may either be in feet or meters.
897                    If the letter 'M' or 'm' is discovered in
898                    the string, then this is an indication that
899                    the value given is expressed in meters, and
900                    must be converted to feet before exiting. */
901
902                 for (x=0; string[x]!='M' && string[x]!='m' && string[x]!=0 && x<48; x++);
903                 if (string[x]=='M' || string[x]=='m')
904                 {
905                         string[x]=0;
906                         sscanf(string,"%lf",&tempsite.alt);
907                         tempsite.alt*=3.28084;
908                 }
909
910                 else
911                 {
912                         string[x]=0;
913                         sscanf(string,"%lf",&tempsite.alt);
914                 }
915         }
916
917         return tempsite;
918 }
919
920 int LoadSDF_SDF(char *name)
921 {
922         /* This function reads uncompressed SPLAT Data Files (.sdf)
923            containing digital elevation model data into memory.
924            Elevation data, maximum and minimum elevations, and
925            quadrangle limits are stored in the first available
926            dem[] structure. */
927
928         int x, y, data, indx, minlat, minlon, maxlat, maxlon;
929         char found, free_slot=0, line[20], sdf_file[255], path_plus_name[255];
930         FILE *fd;
931
932         for (x=0; name[x]!='.' && name[x]!=0 && x<250; x++)
933                 sdf_file[x]=name[x];
934
935         sdf_file[x]=0;
936
937         /* Parse filename for minimum latitude and longitude values */
938
939         sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
940
941         sdf_file[x]='.';
942         sdf_file[x+1]='s';
943         sdf_file[x+2]='d';
944         sdf_file[x+3]='f';
945         sdf_file[x+4]=0;
946
947         /* Is it already in memory? */
948
949         for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
950         {
951                 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
952                         found=1;
953         }
954
955         /* Is room available to load it? */
956
957         if (found==0)
958         {       
959                 for (indx=0, free_slot=0; indx<MAXSLOTS && free_slot==0; indx++)
960                         if (dem[indx].max_north==-90)
961                                 free_slot=1;
962         }
963
964         indx--;
965
966         if (free_slot && found==0 && indx>=0 && indx<MAXSLOTS)
967         {
968                 /* Search for SDF file in current working directory first */
969
970                 strncpy(path_plus_name,sdf_file,255);
971
972                 fd=fopen(path_plus_name,"rb");
973
974                 if (fd==NULL)
975                 {
976                         /* Next, try loading SDF file from path specified
977                            in $HOME/.splat_path file or by -d argument */
978
979                         strncpy(path_plus_name,sdf_path,255);
980                         strncat(path_plus_name,sdf_file,255);
981
982                         fd=fopen(path_plus_name,"rb");
983                 }
984
985                 if (fd!=NULL)
986                 {
987                         fprintf(stdout,"Loading \"%s\" into slot %d...",path_plus_name,indx+1);
988                         fflush(stdout);
989
990                         fgets(line,19,fd);
991                         sscanf(line,"%d",&dem[indx].max_west);
992
993                         fgets(line,19,fd);
994                         sscanf(line,"%d",&dem[indx].min_north);
995
996                         fgets(line,19,fd);
997                         sscanf(line,"%d",&dem[indx].min_west);
998
999                         fgets(line,19,fd);
1000                         sscanf(line,"%d",&dem[indx].max_north);
1001
1002                         for (x=0; x<1200; x++)
1003                                 for (y=0; y<1200; y++)
1004                                 {
1005                                         fgets(line,19,fd);
1006                                         sscanf(line,"%d",&data);
1007
1008                                         dem[indx].data[x][y]=data;
1009
1010                                         if (data>dem[indx].max_el)
1011                                                 dem[indx].max_el=data;
1012
1013                                         if (data<dem[indx].min_el)
1014                                                 dem[indx].min_el=data;
1015                                 }
1016
1017                         fclose(fd);
1018
1019                         if (dem[indx].min_el<min_elevation)
1020                                 min_elevation=dem[indx].min_el;
1021
1022                         if (dem[indx].max_el>max_elevation)
1023                                 max_elevation=dem[indx].max_el;
1024
1025                         if (max_north==-90)
1026                                 max_north=dem[indx].max_north;
1027
1028                         else if (dem[indx].max_north>max_north)
1029                                 max_north=dem[indx].max_north;
1030
1031                         if (min_north==90)
1032                                 min_north=dem[indx].min_north;
1033
1034                         else if (dem[indx].min_north<min_north)
1035                                 min_north=dem[indx].min_north;
1036
1037                         if (max_west==-1)
1038                                 max_west=dem[indx].max_west;
1039
1040                         else
1041                         {
1042                                 if (abs(dem[indx].max_west-max_west)<180)
1043                                 {
1044                                         if (dem[indx].max_west>max_west)
1045                                                 max_west=dem[indx].max_west;
1046                                 }
1047
1048                                 else
1049                                 {
1050                                         if (dem[indx].max_west<max_west)
1051                                                 max_west=dem[indx].max_west;
1052                                 }
1053                         }
1054
1055                         if (min_west==360)
1056                                 min_west=dem[indx].min_west;
1057
1058                         else
1059                         {
1060                                 if (abs(dem[indx].min_west-min_west)<180)
1061                                 {
1062                                         if (dem[indx].min_west<min_west)
1063                                                 min_west=dem[indx].min_west;
1064                                 }
1065
1066                                 else
1067                                 {
1068                                         if (dem[indx].min_west>min_west)
1069                                                 min_west=dem[indx].min_west;
1070                                 }
1071                         }
1072
1073                         fprintf(stdout," Done!\n");
1074                         fflush(stdout);
1075                         return 1;
1076                 }
1077
1078                 else
1079                         return -1;
1080         }
1081
1082         else
1083                 return 0;
1084 }
1085
1086 char *BZfgets(BZFILE *bzfd, unsigned length)
1087 {
1088         /* This function returns at most one less than 'length' number
1089            of characters from a bz2 compressed file whose file descriptor
1090            is pointed to by *bzfd.  In operation, a buffer is filled with
1091            uncompressed data (size = BZBUFFER), which is then parsed
1092            and doled out as NULL terminated character strings every time
1093            this function is invoked.  A NULL string indicates an EOF
1094            or error condition. */
1095
1096         static int x, y, nBuf;
1097         static char buffer[BZBUFFER+1], output[BZBUFFER+1];
1098         char done=0;
1099
1100         if (opened!=1 && bzerror==BZ_OK)
1101         {
1102                 /* First time through.  Initialize everything! */
1103
1104                 x=0;
1105                 y=0;
1106                 nBuf=0;
1107                 opened=1;
1108                 output[0]=0;
1109         }
1110
1111         do
1112         {
1113                 if (x==nBuf && bzerror!=BZ_STREAM_END && bzerror==BZ_OK && opened)
1114                 {
1115                         /* Uncompress data into a static buffer */
1116
1117                         nBuf=BZ2_bzRead(&bzerror, bzfd, buffer, BZBUFFER);
1118                         buffer[nBuf]=0;
1119                         x=0;
1120                 }
1121
1122                 /* Build a string from buffer contents */
1123
1124                 output[y]=buffer[x];
1125
1126                 if (output[y]=='\n' || output[y]==0 || y==(int)length-1)
1127                 {
1128                         output[y+1]=0;
1129                         done=1;
1130                         y=0;
1131                 }
1132
1133                 else
1134                         y++;
1135                 x++;
1136
1137         } while (done==0);
1138
1139         if (output[0]==0)
1140                 opened=0;
1141
1142         return (output);
1143 }
1144
1145 int LoadSDF_BZ(char *name)
1146 {
1147         /* This function reads .bz2 compressed SPLAT Data Files containing
1148            digital elevation model data into memory.  Elevation data,
1149            maximum and minimum elevations, and quadrangle limits are
1150            stored in the first available dem[] structure. */
1151
1152         int x, y, data, indx, minlat, minlon, maxlat, maxlon;
1153         char found, free_slot=0, sdf_file[255], path_plus_name[255];
1154         FILE *fd;
1155         BZFILE *bzfd;
1156
1157         for (x=0; name[x]!='.' && name[x]!=0 && x<247; x++)
1158                 sdf_file[x]=name[x];
1159
1160         sdf_file[x]=0;
1161
1162         /* Parse sdf_file name for minimum latitude and longitude values */
1163
1164         sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1165
1166         sdf_file[x]='.';
1167         sdf_file[x+1]='s';
1168         sdf_file[x+2]='d';
1169         sdf_file[x+3]='f';
1170         sdf_file[x+4]='.';
1171         sdf_file[x+5]='b';
1172         sdf_file[x+6]='z';
1173         sdf_file[x+7]='2';
1174         sdf_file[x+8]=0;
1175
1176         /* Is it already in memory? */
1177
1178         for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
1179         {
1180                 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1181                         found=1;
1182         }
1183
1184         /* Is room available to load it? */
1185
1186         if (found==0)
1187         {       
1188                 for (indx=0, free_slot=0; indx<MAXSLOTS && free_slot==0; indx++)
1189                         if (dem[indx].max_north==-90)
1190                                 free_slot=1;
1191         }
1192
1193         indx--;
1194
1195         if (free_slot && found==0 && indx>=0 && indx<MAXSLOTS)
1196         {
1197                 /* Search for SDF file in current working directory first */
1198
1199                 strncpy(path_plus_name,sdf_file,255);
1200
1201                 fd=fopen(path_plus_name,"rb");
1202                 bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1203
1204                 if (fd==NULL || bzerror!=BZ_OK)
1205                 {
1206                         /* Next, try loading SDF file from path specified
1207                            in $HOME/.splat_path file or by -d argument */
1208
1209                         strncpy(path_plus_name,sdf_path,255);
1210                         strncat(path_plus_name,sdf_file,255);
1211
1212                         fd=fopen(path_plus_name,"rb");
1213                         bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1214                 }
1215
1216                 if (fd!=NULL && bzerror==BZ_OK)
1217                 {
1218                         fprintf(stdout,"Loading \"%s\" into slot %d...",path_plus_name,indx+1);
1219                         fflush(stdout);
1220
1221                         sscanf(BZfgets(bzfd,255),"%d",&dem[indx].max_west);
1222                         sscanf(BZfgets(bzfd,255),"%d",&dem[indx].min_north);
1223                         sscanf(BZfgets(bzfd,255),"%d",&dem[indx].min_west);
1224                         sscanf(BZfgets(bzfd,255),"%d",&dem[indx].max_north);
1225         
1226                         for (x=0; x<1200; x++)
1227                                 for (y=0; y<1200; y++)
1228                                 {
1229                                         sscanf(BZfgets(bzfd,20),"%d",&data);
1230
1231                                         dem[indx].data[x][y]=data;
1232
1233                                         if (data>dem[indx].max_el)
1234                                                 dem[indx].max_el=data;
1235
1236                                         if (data<dem[indx].min_el)
1237                                                 dem[indx].min_el=data;
1238                                 }
1239
1240                         fclose(fd);
1241
1242                         BZ2_bzReadClose(&bzerror,bzfd);
1243
1244                         if (dem[indx].min_el<min_elevation)
1245                                 min_elevation=dem[indx].min_el;
1246         
1247                         if (dem[indx].max_el>max_elevation)
1248                                 max_elevation=dem[indx].max_el;
1249
1250                         if (max_north==-90)
1251                                 max_north=dem[indx].max_north;
1252
1253                         else if (dem[indx].max_north>max_north)
1254                                 max_north=dem[indx].max_north;
1255
1256                         if (min_north==90)
1257                                 min_north=dem[indx].min_north;
1258
1259                         else if (dem[indx].min_north<min_north)
1260                                 min_north=dem[indx].min_north;
1261
1262                         if (max_west==-1)
1263                                 max_west=dem[indx].max_west;
1264
1265                         else
1266                         {
1267                                 if (abs(dem[indx].max_west-max_west)<180)
1268                                 {
1269                                         if (dem[indx].max_west>max_west)
1270                                                 max_west=dem[indx].max_west;
1271                                 }
1272
1273                                 else
1274                                 {
1275                                         if (dem[indx].max_west<max_west)
1276                                                 max_west=dem[indx].max_west;
1277                                 }
1278                         }
1279
1280                         if (min_west==360)
1281                                 min_west=dem[indx].min_west;
1282
1283                         else
1284                         {
1285                                 if (abs(dem[indx].min_west-min_west)<180)
1286                                 {
1287                                         if (dem[indx].min_west<min_west)
1288                                                 min_west=dem[indx].min_west;
1289                                 }
1290
1291                                 else
1292                                 {
1293                                         if (dem[indx].min_west>min_west)
1294                                                 min_west=dem[indx].min_west;
1295                                 }
1296                         }
1297
1298                         fprintf(stdout," Done!\n");
1299                         fflush(stdout);
1300                         return 1;
1301                 }
1302                 else
1303                         return -1;
1304         }
1305         else
1306                 return 0;
1307 }
1308
1309 char LoadSDF(char *name)
1310 {
1311         /* This function loads the requested SDF file from the filesystem.
1312            It first tries to invoke the LoadSDF_SDF() function to load an
1313            uncompressed SDF file (since uncompressed files load slightly
1314            faster).  If that attempt fails, then it tries to load a
1315            compressed SDF file by invoking the LoadSDF_BZ() function.
1316            If that fails, then we can assume that no elevation data
1317            exists for the region requested, and that the region
1318            requested must be entirely over water. */
1319
1320         int x, y, indx, minlat, minlon, maxlat, maxlon;
1321         char found, free_slot=0;
1322         int  return_value=-1;
1323
1324         /* Try to load an uncompressed SDF first. */
1325
1326         return_value=LoadSDF_SDF(name);
1327
1328         /* If that fails, try loading a compressed SDF. */
1329
1330         if (return_value==0 || return_value==-1)
1331                 return_value=LoadSDF_BZ(name);
1332
1333         /* If neither format can be found, then assume the area is water. */
1334
1335         if (return_value==0 || return_value==-1)
1336         {
1337                 /* Parse SDF name for minimum latitude and longitude values */
1338
1339                 sscanf(name,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1340
1341                 /* Is it already in memory? */
1342
1343                 for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
1344                 {
1345                         if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1346                                 found=1;
1347                 }
1348
1349                 /* Is room available to load it? */
1350
1351                 if (found==0)
1352                 {       
1353                         for (indx=0, free_slot=0; indx<MAXSLOTS && free_slot==0; indx++)
1354                                 if (dem[indx].max_north==-90)
1355                                         free_slot=1;
1356                 }
1357
1358                 indx--;
1359
1360                 if (free_slot && found==0 && indx>=0 && indx<MAXSLOTS)
1361                 {
1362                         fprintf(stdout,"Region  \"%s\" assumed as sea-level into slot %d...",name,indx+1);
1363                         fflush(stdout);
1364
1365                         dem[indx].max_west=maxlon;
1366                         dem[indx].min_north=minlat;
1367                         dem[indx].min_west=minlon;
1368                         dem[indx].max_north=maxlat;
1369
1370                         /* Fill DEM with sea-level topography */
1371
1372                         for (x=0; x<1200; x++)
1373                                 for (y=0; y<1200; y++)
1374                                 {
1375                                         dem[indx].data[x][y]=0;
1376
1377                                         if (dem[indx].min_el>0)
1378                                                 dem[indx].min_el=0;
1379                                 }
1380
1381                         if (dem[indx].min_el<min_elevation)
1382                                 min_elevation=dem[indx].min_el;
1383
1384                         if (dem[indx].max_el>max_elevation)
1385                                 max_elevation=dem[indx].max_el;
1386
1387                         if (max_north==-90)
1388                                 max_north=dem[indx].max_north;
1389
1390                         else if (dem[indx].max_north>max_north)
1391                                 max_north=dem[indx].max_north;
1392
1393                         if (min_north==90)
1394                                 min_north=dem[indx].min_north;
1395
1396                         else if (dem[indx].min_north<min_north)
1397                                 min_north=dem[indx].min_north;
1398
1399                         if (max_west==-1)
1400                                 max_west=dem[indx].max_west;
1401
1402                         else
1403                         {
1404                                 if (abs(dem[indx].max_west-max_west)<180)
1405                                 {
1406                                         if (dem[indx].max_west>max_west)
1407                                                 max_west=dem[indx].max_west;
1408                                 }
1409
1410                                 else
1411                                 {
1412                                         if (dem[indx].max_west<max_west)
1413                                                 max_west=dem[indx].max_west;
1414                                 }
1415                         }
1416
1417                         if (min_west==360)
1418                                 min_west=dem[indx].min_west;
1419
1420                         else
1421                         {
1422                                 if (abs(dem[indx].min_west-min_west)<180)
1423                                 {
1424                                         if (dem[indx].min_west<min_west)
1425                                                 min_west=dem[indx].min_west;
1426                                 }
1427
1428                                 else
1429                                 {
1430                                         if (dem[indx].min_west>min_west)
1431                                                 min_west=dem[indx].min_west;
1432                                 }
1433                         }
1434
1435                         fprintf(stdout," Done!\n");
1436                         fflush(stdout);
1437
1438                         return_value=1;
1439                 }
1440         }
1441
1442         return return_value;
1443 }
1444
1445 void LoadCities(char *filename)
1446 {
1447         /* This function reads SPLAT! city/site files, and plots
1448            the locations and names of the cities and site locations
1449            read on topographic maps generated by SPLAT! */
1450
1451         int x, y, z;
1452         char input[80], str[3][80];
1453         struct site city_site;
1454         FILE *fd=NULL;
1455
1456         fd=fopen(filename,"r");
1457
1458         if (fd!=NULL)
1459         {
1460                 fgets(input,78,fd);
1461
1462                 fprintf(stdout,"Reading \"%s\"... ",filename);
1463                 fflush(stdout);
1464
1465                 while (fd!=NULL && feof(fd)==0)
1466                 {
1467                         /* Parse line for name, latitude, and longitude */
1468
1469                         for (x=0, y=0, z=0; x<78 && input[x]!=0 && z<3; x++)
1470                         {
1471                                 if (input[x]!=',' && y<78)
1472                                 {
1473                                         str[z][y]=input[x];
1474                                         y++;
1475                                 }
1476
1477                                 else
1478                                 {
1479                                         str[z][y]=0;
1480                                         z++;
1481                                         y=0;
1482                                 }
1483                         }
1484
1485                         strncpy(city_site.name,str[0],49);
1486                         city_site.lat=ReadBearing(str[1]);
1487                         city_site.lon=ReadBearing(str[2]);
1488                         city_site.alt=0.0;
1489
1490                         PlaceMarker(city_site);
1491
1492                         fgets(input,78,fd);
1493                 }
1494
1495                 fclose(fd);
1496                 fprintf(stdout,"Done!\n");
1497                 fflush(stdout);
1498         }
1499         else
1500                 fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename);
1501 }
1502
1503 void LoadBoundaries(char *filename)
1504 {
1505         /* This function reads Cartographic Boundary Files available from
1506            the U.S. Census Bureau, and plots the data contained in those
1507            files on the PPM Map generated by SPLAT!.  Such files contain
1508            the coordinates that describe the boundaries of cities,
1509            counties, and states. */
1510
1511         int x;
1512         double lat0, lon0, lat1, lon1;
1513         char string[80];
1514         struct site source, destination;
1515         FILE *fd=NULL;
1516
1517         fd=fopen(filename,"r");
1518
1519         if (fd!=NULL)
1520         {
1521                 fgets(string,78,fd);
1522
1523                 fprintf(stdout,"Reading \"%s\"... ",filename);
1524                 fflush(stdout);
1525
1526                 do
1527                 {
1528                         fgets(string,78,fd);
1529                         sscanf(string,"%lf %lf", &lon0, &lat0);
1530                         fgets(string,78,fd);
1531
1532                         do
1533                         {
1534                                 sscanf(string,"%lf %lf", &lon1, &lat1);
1535
1536                                 lon0=fabs(lon0);
1537                                 lon1=fabs(lon1);
1538
1539                                 source.lat=lat0;
1540                                 source.lon=lon0;
1541                                 destination.lat=lat1;
1542                                 destination.lon=lon1;
1543
1544                                 ReadPath(source,destination);
1545
1546                                 for (x=0; x<path.length; x++)
1547                                         OrMask(path.lat[x],path.lon[x],4);
1548
1549                                 lat0=lat1;
1550                                 lon0=lon1;
1551
1552                                 fgets(string,78,fd);
1553
1554                         } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
1555
1556                         fgets(string,78,fd);
1557
1558                 } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
1559
1560                 fclose(fd);
1561
1562                 fprintf(stdout,"Done!\n");
1563                 fflush(stdout);
1564         }
1565
1566         else
1567                 fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename);
1568 }
1569
1570 void ReadLRParm(char *txsite_filename)
1571 {
1572         /* This function reads Longley-Rice parameter data for the
1573            transmitter site.  The file name is the same as the txsite,
1574            except the filename extension is .lrp.  If the needed file
1575            is not found, then the file "splat.lrp" is read from the
1576            current working directory.  Failure to load this file will
1577            result in the default parameters hard coded into this
1578            being used and written to "splat.lrp". */
1579
1580         double din;
1581         char filename[255], lookup[256], string[80];
1582         int iin, ok=0, x;
1583         FILE *fd=NULL, *outfile=NULL;
1584
1585         /* Default parameters in case things go bad */
1586
1587         LR.eps_dielect=15.0;
1588         LR.sgm_conductivity=0.005;
1589         LR.eno_ns_surfref=301.0;
1590         LR.frq_mhz=300.0;
1591         LR.radio_climate=5;
1592         LR.pol=0;
1593         LR.conf=0.50;
1594         LR.rel=0.50;
1595
1596         /* Modify txsite filename to one with a .lrp extension. */
1597
1598         strncpy(filename,txsite_filename,255);
1599
1600         for (x=0; filename[x]!='.' && filename[x]!=0 && filename[x]!='\n' && x<249; x++);
1601
1602         filename[x]='.';
1603         filename[x+1]='l';
1604         filename[x+2]='r';
1605         filename[x+3]='p';
1606         filename[x+4]=0;
1607
1608         /* Small lookup table to parse file, pass
1609            numeric data, and ignore comments. */
1610
1611         for (x=0; x<=255; x++)
1612                 lookup[x]=0;
1613
1614         /* Valid characters */
1615
1616         for (x=48; x<=57; x++)
1617                 lookup[x]=x;
1618
1619         lookup[32]=32;
1620         lookup[46]='.';
1621
1622         fd=fopen(filename,"r");
1623
1624         if (fd==NULL)
1625         {
1626                 /* Load default "splat.lrp" file */
1627
1628                 strncpy(filename,"splat.lrp\0",10);
1629                 fd=fopen(filename,"r");
1630         }
1631
1632         if (fd!=NULL)
1633         {
1634                 fgets(string,80,fd);
1635
1636                 for (x=0; lookup[(int)string[x]] && x<20; x++)
1637                         string[x]=lookup[(int)string[x]];
1638
1639                 string[x]=0;
1640
1641                 ok=sscanf(string,"%lf", &din);
1642
1643                 if (ok)
1644                 {
1645                         LR.eps_dielect=din;
1646
1647                         fgets(string,80,fd);
1648
1649                         for (x=0; lookup[(int)string[x]] && x<20; x++)
1650                                 string[x]=lookup[(int)string[x]];
1651
1652                         string[x]=0;
1653
1654                         ok=sscanf(string,"%lf", &din);
1655                 }
1656
1657                 if (ok)
1658                 {
1659                         LR.sgm_conductivity=din;
1660
1661                         fgets(string,80,fd);
1662
1663                         for (x=0; lookup[(int)string[x]] && x<20; x++)
1664                                 string[x]=lookup[(int)string[x]];
1665
1666                         string[x]=0;
1667
1668                         ok=sscanf(string,"%lf", &din);
1669                 }
1670
1671                 if (ok)
1672                 {
1673                         LR.eno_ns_surfref=din;
1674
1675                         fgets(string,80,fd);
1676
1677                         for (x=0; lookup[(int)string[x]] && x<20; x++)
1678                                 string[x]=lookup[(int)string[x]];
1679
1680                         string[x]=0;
1681
1682                         ok=sscanf(string,"%lf", &din);
1683                 }
1684
1685                 if (ok)
1686                 {
1687                         LR.frq_mhz=din;
1688
1689                         fgets(string,80,fd);
1690
1691                         for (x=0; lookup[(int)string[x]] && x<20; x++)
1692                                 string[x]=lookup[(int)string[x]];
1693
1694                         string[x]=0;
1695
1696                         ok=sscanf(string,"%d", &iin);
1697                 }
1698
1699                 if (ok)
1700                 {
1701                         LR.radio_climate=iin;
1702
1703                         fgets(string,80,fd);
1704
1705                         for (x=0; lookup[(int)string[x]] && x<20; x++)
1706                                 string[x]=lookup[(int)string[x]];
1707
1708                         string[x]=0;
1709
1710                         ok=sscanf(string,"%d", &iin);
1711                 }
1712
1713                 if (ok)
1714                 {
1715                         LR.pol=iin;
1716
1717                         fgets(string,80,fd);
1718
1719                         for (x=0; lookup[(int)string[x]] && x<20; x++)
1720                                 string[x]=lookup[(int)string[x]];
1721
1722                         string[x]=0;
1723
1724                         ok=sscanf(string,"%lf", &din);
1725                 }
1726
1727                 if (ok)
1728                 {
1729                         LR.conf=din;
1730
1731                         fgets(string,80,fd);
1732
1733                         for (x=0; lookup[(int)string[x]] && x<20; x++)
1734                                 string[x]=lookup[(int)string[x]];
1735
1736                         string[x]=0;
1737
1738                         ok=sscanf(string,"%lf", &din);
1739                 }
1740
1741                 if (ok)
1742                         LR.rel=din;
1743
1744                 fclose(fd);
1745         } 
1746
1747         if (fd==NULL)
1748         {
1749                 /* Create a "splat.lrp" file since one
1750                    could not be successfully loaded. */
1751
1752                 outfile=fopen("splat.lrp","w");
1753
1754                 fprintf(outfile,"%.3f\t; Earth Dielectric Constant (Relative permittivity)\n",LR.eps_dielect);
1755
1756                 fprintf(outfile,"%.3f\t; Earth Conductivity (Siemens per meter)\n", LR.sgm_conductivity);
1757
1758                 fprintf(outfile,"%.3f\t; Atmospheric Bending Constant (N-Units)\n",LR.eno_ns_surfref);
1759
1760                 fprintf(outfile,"%.3f\t; Frequency in MHz (20 MHz to 20 GHz)\n", LR.frq_mhz);
1761
1762                 fprintf(outfile,"%d\t; Radio Climate\n",LR.radio_climate);
1763
1764                 fprintf(outfile,"%d\t; Polarization (0 = Horizontal, 1 = Vertical)\n", LR.pol);
1765
1766                 fprintf(outfile,"%.2f\t; Fraction of situations\n",LR.conf);
1767
1768                 fprintf(outfile, "%.2f\t; Fraction of time\n",LR.rel);
1769
1770                 fprintf(outfile,"\nPlease consult SPLAT! documentation for the meaning and use of this data.\n");
1771
1772                 fclose(outfile);
1773
1774                 fprintf(stderr,"\n%c*** There were problems reading your \"%s\" file! ***\nA \"splat.lrp\" file was written to your directory with default data.\n",7,filename);
1775         }
1776
1777         if (fd==NULL || ok==0)
1778                 fprintf(stderr,"Longley-Rice default parameters have been assumed for this analysis.\n");
1779 }
1780
1781 struct site los(struct site source, struct site destination)
1782 {
1783         /* This function determines whether a line-of-sight path
1784            unobstructed by terrain exists between source (transmitter)
1785            and destination (receiver) based on the geographical
1786            locations of the two sites, their respective antenna
1787            heights above ground, and the terrain between them.
1788            A site structure is returned upon completion.  If the
1789            first character of site.name is ' ', then a clear path
1790            exists between source and destination.  If the first
1791            character is '*', then an obstruction exists, and the
1792            site.lat and site.lon elements of the structure provide
1793            the geographical location of the obstruction. */
1794            
1795         int x;
1796         char block;
1797         struct site test, blockage;
1798         register double distance, tx_alt, rx_alt,
1799                  cos_xmtr_angle, cos_test_angle, test_alt;
1800
1801         ReadPath(source,destination);
1802
1803         distance=5280.0*Distance(source,destination);
1804         tx_alt=earthradius+source.alt+GetElevation(source);
1805         rx_alt=earthradius+destination.alt+GetElevation(destination);
1806
1807         /* Elevation angle of the xmtr (source) as seen by the rcvr */
1808
1809         cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
1810
1811         /* Determine the elevation angle of each discrete location
1812            along the path between the receiver and transmitter.
1813
1814            Since obstructions are more likely due to terrain effects
1815            closest to the receiver rather than farther away, we start
1816            looking for potential obstructions from the receiver's
1817            location, and work our way towards the transmitter.
1818            This loop is broken when the first obstruction is
1819            detected.  If we can travel all the way to the transmitter
1820            without detecting an obstruction, then we have a clear
1821            unobstructed path between transmitter and receiver. */
1822
1823         for (x=path.length-1, block=0; x>0 && block==0; x--)
1824         {
1825                 /* Build a structure for each test
1826                    point along the path to be surveyed. */
1827
1828                 test.lat=path.lat[x];
1829                 test.lon=path.lon[x];
1830
1831                 /* Measure the distance between the
1832                    test point and the receiver locations */
1833
1834                 distance=5280.0*Distance(test,destination);
1835                 test_alt=earthradius+path.elevation[x];
1836
1837                 /* Determine the cosine of the elevation of the test
1838                    point as seen from the location of the receiver */
1839
1840                 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
1841
1842                 /* If the elevation angle to the test point (as seen from
1843                    the receiver) is greater than the elevation angle to the
1844                    transmitter (as seen by the receiver), then we have a
1845                    path obstructed by terrain.  Note: Since we're comparing
1846                    the cosines of these angles rather than the angles
1847                    themselves (eliminating the call to acos() saves
1848                    considerable time), the following "if" statement is
1849                    reversed from what it would normally be if the angles
1850                    were compared. */
1851
1852                 if (cos_xmtr_angle>cos_test_angle)
1853                 {
1854                         block=1;
1855                         blockage.lat=path.lat[x];
1856                         blockage.lon=path.lon[x];
1857                         blockage.alt=path.elevation[x];
1858                         blockage.name[0]='*';
1859                 }
1860         }
1861
1862         if (block==0)
1863         {
1864                 blockage.lat=0.0;
1865                 blockage.lon=0.0;
1866                 blockage.alt=0.0;
1867                 blockage.name[0]=' ';
1868         }
1869
1870         return blockage;
1871 }
1872
1873 void PlotPath(struct site source, struct site destination, char mask_value)
1874 {
1875         /* This function analyzes the path between the source and
1876            destination locations.  It determines which points along
1877            the path have line-of-sight visibility to the source.
1878            Points along with path having line-of-sight visibility
1879            to the source at an AGL altitude equal to that of the
1880            destination location are stored by setting bit 1 in the
1881            mask[][] array, which are displayed in green when PPM
1882            maps are later generated by SPLAT!. */
1883
1884         char block;
1885         int x, y;
1886         register double cos_xmtr_angle, cos_test_angle, test_alt;
1887         double distance, rx_alt, tx_alt;
1888
1889         ReadPath(source,destination);
1890
1891         for (y=0; y<path.length; y++)
1892         {
1893                 /* Test this point only if it hasn't been already
1894                    tested and found to be free of obstructions. */
1895
1896                 if ((GetMask(path.lat[y],path.lon[y])&mask_value)==0)
1897                 {
1898                         distance=5280.0*path.distance[y];
1899                         tx_alt=earthradius+source.alt+path.elevation[0];
1900                         rx_alt=earthradius+destination.alt+path.elevation[y];
1901
1902                         /* Calculate the cosine of the elevation of the
1903                            transmitter as seen at the temp rx point. */
1904
1905                         cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
1906
1907                         for (x=y, block=0; x>=0 && block==0; x--)
1908                         {
1909                                 distance=5280.0*(path.distance[y]-path.distance[x]);
1910                                 test_alt=earthradius+path.elevation[x];
1911
1912                                 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
1913
1914                                 /* Compare these two angles to determine if
1915                                    an obstruction exists.  Since we're comparing
1916                                    the cosines of these angles rather than
1917                                    the angles themselves, the following "if"
1918                                    statement is reversed from what it would
1919                                    be if the actual angles were compared. */
1920
1921                                 if (cos_xmtr_angle>cos_test_angle)
1922                                         block=1;
1923                         }
1924
1925                         if (block==0)
1926                                 OrMask(path.lat[y],path.lon[y],mask_value);
1927                 }
1928         }
1929 }
1930
1931 void PlotLRPath(struct site source, struct site destination)
1932 {
1933         /* This function plots the RF signal path loss
1934            between source and destination points based
1935            on the Longley-Rice propagation model. */
1936
1937         char strmode[100];
1938         int x, y, errnum;
1939         double loss;
1940
1941         ReadPath(source,destination);
1942         elev_l[1]=0.04*METERS_PER_MILE;
1943
1944         for (x=0; x<path.length; x++)
1945                 elev_l[x+2]=path.elevation[x]*METERS_PER_FOOT;  
1946          
1947         for (y=0; y<path.length; y++)
1948         {
1949                 /* Test this point only if it has not already been tested. */
1950
1951                 if (GetMask(path.lat[y],path.lon[y])==0 && 0.04*y<=max_range)
1952                 {
1953                         elev_l[0]=y+1;
1954
1955                         point_to_point(elev_l,source.alt*METERS_PER_FOOT, 
1956                         destination.alt*METERS_PER_FOOT,
1957                         LR.eps_dielect, LR.sgm_conductivity, LR.eno_ns_surfref,
1958                         LR.frq_mhz, LR.radio_climate, LR.pol, LR.conf, LR.rel,
1959                         loss, strmode, errnum);
1960
1961                         /* Note: PASS BY REFERENCE ... loss and errnum are
1962                            passed by reference, and are only used in this
1963                            file by this function */
1964
1965                         if (loss>225.0)
1966                                 loss=225.0;
1967
1968                         if (loss<75.0)
1969                                 loss=75.0;
1970
1971                         loss-=75.0;
1972                         loss/=10.0;
1973                         loss+=1.0;
1974                 
1975                         OrMask(path.lat[y],path.lon[y],((unsigned char)(loss))<<3);
1976                 }
1977
1978                 else if (GetMask(path.lat[y],path.lon[y])==0 && 0.04*y>max_range)
1979                         OrMask(path.lat[y],path.lon[y],1);
1980         }
1981 }
1982
1983 void PlotCoverage(struct site source, double altitude)
1984 {
1985         /* This function performs a 360 degree sweep around the
1986            transmitter site (source location), and plots the
1987            line-of-sight coverage of the transmitter on the SPLAT!
1988            generated topographic map based on a receiver located
1989            at the specified altitude (in feet AGL).  Results
1990            are stored in memory, and written out in the form
1991            of a topographic map when the WritePPM() function
1992            is later invoked. */
1993
1994         float lat, lon, one_pixel;
1995         static unsigned char mask_value;
1996         int z, count;
1997         struct site edge;
1998         unsigned char symbol[4], x;
1999
2000         /* Initialize mask_value */
2001
2002         if (mask_value!=8 && mask_value!=16 && mask_value!=32)
2003                 mask_value=1;
2004
2005         one_pixel=1.0/1200.0;
2006
2007         symbol[0]='.';
2008         symbol[1]='o';
2009         symbol[2]='O';
2010         symbol[3]='o';
2011
2012         count=0;        
2013
2014         fprintf(stdout,"\nComputing line-of-sight coverage of %s with an RX antenna\nat %.2f feet AGL:\n\n 0%c to  25%c ",source.name,altitude,37,37);
2015         fflush(stdout);
2016
2017         /* 18.75=1200 pixels/degree divided by 64 loops
2018            per progress indicator symbol (.oOo) printed. */
2019
2020         z=(int)(18.75*ReduceAngle(max_west-min_west));
2021
2022         for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2023         {
2024                 if (lon>=360.0)
2025                         lon-=360.0;
2026
2027                 edge.lat=max_north;
2028                 edge.lon=lon;
2029                 edge.alt=altitude;
2030
2031                 PlotPath(source,edge,mask_value);
2032                 count++;
2033
2034                 if (count==z) 
2035                 {
2036                         fprintf(stdout,"%c",symbol[x]);
2037                         fflush(stdout);
2038                         count=0;
2039
2040                         if (x==3)
2041                                 x=0;
2042                         else
2043                                 x++;
2044                 }
2045         }
2046
2047         count=0;
2048         fprintf(stdout,"\n25%c to  50%c ",37,37);
2049         fflush(stdout);
2050         
2051         z=(int)(18.75*(max_north-min_north));
2052
2053         for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel)
2054         {
2055                 edge.lat=lat;
2056                 edge.lon=min_west;
2057                 edge.alt=altitude;
2058
2059                 PlotPath(source,edge,mask_value);
2060                 count++;
2061
2062                 if (count==z) 
2063                 {
2064                         fprintf(stdout,"%c",symbol[x]);
2065                         fflush(stdout);
2066                         count=0;
2067
2068                         if (x==3)
2069                                 x=0;
2070                         else
2071                                 x++;
2072                 }
2073         }
2074
2075         count=0;
2076         fprintf(stdout,"\n50%c to  75%c ",37,37);
2077         fflush(stdout);
2078
2079         z=(int)(18.75*ReduceAngle(max_west-min_west));
2080
2081         for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2082         {
2083                 if (lon>=360.0)
2084                         lon-=360.0;
2085
2086                 edge.lat=min_north;
2087                 edge.lon=lon;
2088                 edge.alt=altitude;
2089
2090                 PlotPath(source,edge,mask_value);
2091                 count++;
2092
2093                 if (count==z)
2094                 {
2095                         fprintf(stdout,"%c",symbol[x]);
2096                         fflush(stdout);
2097                         count=0;
2098
2099                         if (x==3)
2100                                 x=0;
2101                         else
2102                                 x++;
2103                 }
2104         }
2105
2106         count=0;
2107         fprintf(stdout,"\n75%c to 100%c ",37,37);
2108         fflush(stdout);
2109         
2110         z=(int)(18.75*(max_north-min_north));
2111
2112         for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel)
2113         {
2114                 edge.lat=lat;
2115                 edge.lon=max_west;
2116                 edge.alt=altitude;
2117
2118                 PlotPath(source,edge,mask_value);
2119                 count++;
2120
2121                 if (count==z)
2122                 {
2123                         fprintf(stdout,"%c",symbol[x]);
2124                         fflush(stdout);
2125                         count=0;
2126
2127                         if (x==3)
2128                                 x=0;
2129                         else
2130                                 x++;
2131                 }
2132         }
2133
2134         fprintf(stdout,"\nDone!\n");
2135         fflush(stdout);
2136
2137         /* Assign next mask value */
2138
2139         switch (mask_value)
2140         {
2141                 case 1:
2142                         mask_value=8;
2143                         break;
2144
2145                 case 8:
2146                         mask_value=16;
2147                         break;
2148
2149                 case 16:
2150                         mask_value=32;
2151         }
2152 }
2153
2154 void PlotLRMap(struct site source, double altitude)
2155 {
2156         /* This function performs a 360 degree sweep around the
2157            transmitter site (source location), and plots the
2158            Longley-Rice attenuation on the SPLAT! generated
2159            topographic map based on a receiver located at
2160            the specified altitude (in feet AGL).  Results
2161            are stored in memory, and written out in the form
2162            of a topographic map when the WritePPMLR() function
2163            is later invoked. */
2164
2165         int z, count;
2166         struct site edge;
2167         float lat, lon, one_pixel;
2168         unsigned char symbol[4], x;
2169
2170         one_pixel=1.0/1200.0;
2171
2172         symbol[0]='.';
2173         symbol[1]='o';
2174         symbol[2]='O';
2175         symbol[3]='o';
2176
2177         count=0;
2178
2179         fprintf(stdout,"\nComputing Longley-Rice coverage of %s ", source.name);
2180         fprintf(stdout,"out to a radius\nof %.2f miles with an RX antenna at %.2f feet AGL:\n\n 0%c to  25%c ",max_range,altitude,37,37);
2181         fflush(stdout);
2182
2183         /* 18.75=1200 pixels/degree divided by 64 loops
2184            per progress indicator symbol (.oOo) printed. */
2185
2186         z=(int)(18.75*ReduceAngle(max_west-min_west));
2187
2188         for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2189         {
2190                 if (lon>=360.0)
2191                         lon-=360.0;
2192
2193                 edge.lat=max_north;
2194                 edge.lon=lon;
2195                 edge.alt=altitude;
2196
2197                 PlotLRPath(source,edge);
2198                 count++;
2199
2200                 if (count==z) 
2201                 {
2202                         fprintf(stdout,"%c",symbol[x]);
2203                         fflush(stdout);
2204                         count=0;
2205
2206                         if (x==3)
2207                                 x=0;
2208                         else
2209                                 x++;
2210                 }
2211         }
2212
2213         count=0;
2214         fprintf(stdout,"\n25%c to  50%c ",37,37);
2215         fflush(stdout);
2216         
2217         z=(int)(18.75*(max_north-min_north));
2218
2219         for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel)
2220         {
2221                 edge.lat=lat;
2222                 edge.lon=min_west;
2223                 edge.alt=altitude;
2224
2225                 PlotLRPath(source,edge);
2226                 count++;
2227
2228                 if (count==z) 
2229                 {
2230                         fprintf(stdout,"%c",symbol[x]);
2231                         fflush(stdout);
2232                         count=0;
2233
2234                         if (x==3)
2235                                 x=0;
2236                         else
2237                                 x++;
2238                 }
2239         }
2240
2241         count=0;
2242         fprintf(stdout,"\n50%c to  75%c ",37,37);
2243         fflush(stdout);
2244
2245         z=(int)(18.75*ReduceAngle(max_west-min_west));
2246
2247         for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2248         {
2249                 if (lon>=360.0)
2250                         lon-=360.0;
2251
2252                 edge.lat=min_north;
2253                 edge.lon=lon;
2254                 edge.alt=altitude;
2255
2256                 PlotLRPath(source,edge);
2257                 count++;
2258
2259                 if (count==z)
2260                 {
2261                         fprintf(stdout,"%c",symbol[x]);
2262                         fflush(stdout);
2263                         count=0;
2264
2265                         if (x==3)
2266                                 x=0;
2267                         else
2268                                 x++;
2269                 }
2270         }
2271
2272         count=0;
2273         fprintf(stdout,"\n75%c to 100%c ",37,37);
2274         fflush(stdout);
2275         
2276         z=(int)(18.75*(max_north-min_north));
2277
2278         for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel)
2279         {
2280                 edge.lat=lat;
2281                 edge.lon=max_west;
2282                 edge.alt=altitude;
2283
2284                 PlotLRPath(source,edge);
2285                 count++;
2286
2287                 if (count==z)
2288                 {
2289                         fprintf(stdout,"%c",symbol[x]);
2290                         fflush(stdout);
2291                         count=0;
2292
2293                         if (x==3)
2294                                 x=0;
2295                         else
2296                                 x++;
2297                 }
2298         }
2299
2300         fprintf(stdout,"\nDone!\n");
2301         fflush(stdout);
2302 }
2303
2304 void WritePPM(char *filename)
2305 {
2306         /* This function generates a topographic map in Portable Pix Map
2307            (PPM) format based on logarithmically scaled topology data,
2308            as well as the content of flags held in the mask[][] array.
2309            The image created is rotated counter-clockwise 90 degrees
2310            from its representation in dem[][] so that north points
2311            up and east points right in the image generated. */
2312
2313         char mapfile[255];
2314         unsigned char found, mask;
2315         unsigned width, height, output;
2316         int indx, x, y, x0=0, y0=0, minlat, minlon;
2317         float lat, lon, one_pixel, conversion, one_over_gamma;
2318         FILE *fd;
2319
2320         one_pixel=1.0/1200.0;
2321         one_over_gamma=1.0/GAMMA;
2322         conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
2323
2324         width=(unsigned)(1200*ReduceAngle(max_west-min_west));
2325         height=(unsigned)(1200*ReduceAngle(max_north-min_north));
2326
2327         if (filename[0]==0)
2328                 strncpy(mapfile, "map.ppm\0",8);
2329         else
2330         {
2331                 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
2332                         mapfile[x]=filename[x];
2333
2334                 mapfile[x]='.';
2335                 mapfile[x+1]='p';
2336                 mapfile[x+2]='p';
2337                 mapfile[x+3]='m';
2338                 mapfile[x+4]=0;
2339         }
2340
2341         fd=fopen(mapfile,"wb");
2342
2343         fprintf(fd,"P6\n%u %u\n255\n",width,height);
2344
2345         fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height);
2346         fflush(stdout);
2347
2348         for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel)
2349         {
2350                 minlat=(int)floor(lat);
2351
2352                 for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel)
2353                 {
2354                         if (lon<0.0)
2355                                 lon+=360.0;
2356
2357                         minlon=(int)floor(lon);
2358
2359                         for (indx=0, found=0; indx<MAXSLOTS && found==0;)
2360                                 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
2361                                         found=1;
2362                                 else
2363                                         indx++;
2364
2365                         if (found)
2366                         {
2367                                 x0=(int)(1199.0*(lat-floor(lat)));
2368                                 y0=(int)(1199.0*(lon-floor(lon)));
2369
2370                                 mask=dem[indx].mask[x0][y0];
2371
2372                                 if (mask&2)
2373                                         /* Text Labels: Red */
2374                                         fprintf(fd,"%c%c%c",255,0,0);
2375
2376                                 else if (mask&4)
2377                                         /* County Boundaries: Light Cyan */
2378                                         fprintf(fd,"%c%c%c",128,128,255);
2379
2380                                 else switch (mask&57)
2381                                 {
2382                                         case 1:
2383                                         /* TX1: Green */
2384                                         fprintf(fd,"%c%c%c",0,255,0);
2385                                         break;
2386
2387                                         case 8:
2388                                         /* TX2: Cyan */
2389                                         fprintf(fd,"%c%c%c",0,255,255);
2390                                         break;
2391
2392                                         case 9:
2393                                         /* TX1 + TX2: Yellow */
2394                                         fprintf(fd,"%c%c%c",255,255,0);
2395                                         break;
2396
2397                                         case 16:
2398                                         /* TX3: Medium Violet */
2399                                         fprintf(fd,"%c%c%c",147,112,219);
2400                                         break;
2401
2402                                         case 17:
2403                                         /* TX1 + TX3: Pink */
2404                                         fprintf(fd,"%c%c%c",255,192,203);
2405                                         break;
2406
2407                                         case 24:
2408                                         /* TX2 + TX3: Orange */
2409                                         fprintf(fd,"%c%c%c",255,165,0);
2410                                         break;
2411
2412                                         case 25:
2413                                         /* TX1 + TX2 + TX3: Dark Green */
2414                                         fprintf(fd,"%c%c%c",0,100,0);
2415                                         break;
2416
2417                                         case 32:
2418                                         /* TX4: Sienna 1 */
2419                                         fprintf(fd,"%c%c%c",255,130,71);
2420                                         break;
2421
2422                                         case 33:
2423                                         /* TX1 + TX4: Green Yellow */
2424                                         fprintf(fd,"%c%c%c",173,255,47);
2425                                         break;
2426
2427                                         case 40:
2428                                         /* TX2 + TX4: Dark Sea Green 1 */
2429                                         fprintf(fd,"%c%c%c",193,255,193);
2430                                         break;
2431
2432                                         case 41:
2433                                         /* TX1 + TX2 + TX4: Blanched Almond */
2434                                         fprintf(fd,"%c%c%c",255,235,205);
2435                                         break;
2436
2437                                         case 48:
2438                                         /* TX3 + TX4: Dark Turquoise */
2439                                         fprintf(fd,"%c%c%c",0,206,209);
2440                                         break;
2441
2442                                         case 49:
2443                                         /* TX1 + TX3 + TX4: Medium Spring Green */
2444                                         fprintf(fd,"%c%c%c",0,250,154);
2445                                         break;
2446
2447                                         case 56:
2448                                         /* TX2 + TX3 + TX4: Tan */
2449                                         fprintf(fd,"%c%c%c",210,180,140);
2450                                         break;
2451
2452                                         case 57:
2453                                         /* TX1 + TX2 + TX3 + TX4: Gold2 */
2454                                         fprintf(fd,"%c%c%c",238,201,0);
2455                                         break;
2456
2457                                         default:
2458                                         /* Water: Medium Blue */
2459                                         if (dem[indx].data[x0][y0]==0)
2460                                                 fprintf(fd,"%c%c%c",0,0,170);
2461                                         else
2462                                         {
2463                                                 /* Elevation: Greyscale */
2464                                                 output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
2465                                                 fprintf(fd,"%c%c%c",output,output,output);
2466                                         }
2467                                 }
2468                         }
2469
2470                         else
2471                         {
2472                                 /* We should never get here, but if */
2473                                 /* we do, display the region as black */
2474
2475                                 fprintf(fd,"%c%c%c",0,0,0);
2476                         }
2477                 }
2478         }
2479
2480         fclose(fd);
2481         fprintf(stdout,"Done!\n");
2482         fflush(stdout);
2483 }
2484
2485 void WritePPMLR(char *filename)
2486 {
2487         /* This function generates a topographic map in Portable Pix Map
2488            (PPM) format based on the content of flags held in the mask[][] 
2489            array (only).  The image created is rotated counter-clockwise
2490            90 degrees from its representation in dem[][] so that north
2491            points up and east points right in the image generated. */
2492
2493         char mapfile[255];
2494         unsigned width, height, output;
2495         unsigned char found, mask, cityorcounty;
2496         int indx, x, y, t, t2, x0, y0, minlat, minlon, loss;
2497         float lat, lon, one_pixel, conversion, one_over_gamma;
2498         FILE *fd;
2499
2500         one_pixel=1.0/1200.0;
2501         one_over_gamma=1.0/GAMMA;
2502         conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
2503
2504         width=(unsigned)(1200*ReduceAngle(max_west-min_west));
2505         height=(unsigned)(1200*ReduceAngle(max_north-min_north));
2506
2507         if (filename[0]==0)
2508                 strncpy(mapfile, "map.ppm\0",8);
2509         else
2510         {
2511                 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
2512                         mapfile[x]=filename[x];
2513
2514                 mapfile[x]='.';
2515                 mapfile[x+1]='p';
2516                 mapfile[x+2]='p';
2517                 mapfile[x+3]='m';
2518                 mapfile[x+4]=0;
2519         }
2520
2521         fd=fopen(mapfile,"wb");
2522
2523         fprintf(fd,"P6\n%u %u\n255\n",width,height+30);
2524
2525         fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height+30);
2526         fflush(stdout);
2527
2528         for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel)
2529         {
2530                 minlat=(int)floor(lat);
2531
2532                 for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel)
2533                 {
2534                         if (lon<0.0)
2535                                 lon+=360.0;
2536
2537                         minlon=(int)floor(lon);
2538
2539                         for (indx=0, found=0; indx<MAXSLOTS && found==0;)
2540                                 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
2541                                         found=1;
2542                                 else
2543                                         indx++;
2544                         if (found)
2545                         {
2546                                 x0=(int)(1199.0*(lat-floor(lat)));
2547                                 y0=(int)(1199.0*(lon-floor(lon)));
2548
2549                                 mask=dem[indx].mask[x0][y0];
2550                                 loss=70+(10*(int)((mask&248)>>3));
2551                                 cityorcounty=0;
2552
2553                                 if (mask&2)
2554                                 {
2555                                         /* Text Labels - Black or Red */
2556
2557                                         /* if ((mask&120) && (loss<=maxdB)) */
2558                                         if ((mask&120) && (loss<=90))
2559                                                 fprintf(fd,"%c%c%c",0,0,0);
2560                                         else
2561                                                 fprintf(fd,"%c%c%c",255,0,0);
2562
2563                                         cityorcounty=1;
2564                                 }
2565
2566                                 else if (mask&4)
2567                                 {
2568                                         /* County Boundaries: Black */
2569
2570                                         fprintf(fd,"%c%c%c",0,0,0);
2571
2572                                         cityorcounty=1;
2573                                 }
2574
2575                                 if (cityorcounty==0)
2576                                 {
2577                                         if (loss>maxdB)
2578
2579                                         { /* Display land or sea elevation */
2580
2581                                                 if (dem[indx].data[x0][y0]==0)
2582                                                         fprintf(fd,"%c%c%c",0,0,170);
2583                                                 else
2584                                                 {
2585                                                         /* Elevation: Greyscale */
2586                                                         output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
2587                                                         fprintf(fd,"%c%c%c",output,output,output);
2588                                                 }
2589                                         }
2590
2591                                         else switch (loss)
2592                                         {
2593                                                 /* Plot signal loss in color */
2594
2595                                                 case 80:
2596                                                 fprintf(fd,"%c%c%c",255,0,0);
2597                                                 break;
2598
2599                                                 case 90:
2600                                                 fprintf(fd,"%c%c%c",255,128,0);
2601                                                 break;
2602
2603                                                 case 100:
2604                                                 fprintf(fd,"%c%c%c",255,165,0);
2605                                                 break;
2606
2607                                                 case 110:
2608                                                 fprintf(fd,"%c%c%c",255,206,0);
2609                                                 break;
2610
2611                                                 case 120:
2612                                                 fprintf(fd,"%c%c%c",255,255,0);
2613                                                 break;
2614
2615                                                 case 130:
2616                                                 fprintf(fd,"%c%c%c",184,255,0);
2617                                                 break;
2618
2619                                                 case 140:
2620                                                 fprintf(fd,"%c%c%c",0,255,0);
2621                                                 break;
2622
2623                                                 case 150:
2624                                                 fprintf(fd,"%c%c%c",0,208,0);
2625                                                 break;
2626
2627                                                 case 160:
2628                                                 fprintf(fd,"%c%c%c",0,196,196);
2629                                                 break;
2630
2631                                                 case 170:
2632                                                 fprintf(fd,"%c%c%c",0,148,255);
2633                                                 break;
2634
2635                                                 case 180:
2636                                                 fprintf(fd,"%c%c%c",80,80,255);
2637                                                 break;
2638
2639                                                 case 190:
2640                                                 fprintf(fd,"%c%c%c",0,38,255);
2641                                                 break;
2642
2643                                                 case 200:
2644                                                 fprintf(fd,"%c%c%c",142,63,255);
2645                                                 break;
2646
2647                                                 case 210:
2648                                                 fprintf(fd,"%c%c%c",196,54,255);
2649                                                 break;
2650
2651                                                 case 220:
2652                                                 fprintf(fd,"%c%c%c",255,0,255);
2653                                                 break;
2654
2655                                                 case 230:
2656                                                 fprintf(fd,"%c%c%c",255,194,204);
2657                                                 break;
2658
2659                                                 default:
2660
2661                                                 if (dem[indx].data[x0][y0]==0)
2662                                                         fprintf(fd,"%c%c%c",0,0,170);
2663                                                 else
2664                                                 {
2665                                                         /* Elevation: Greyscale */
2666                                                         output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
2667                                                         fprintf(fd,"%c%c%c",output,output,output);
2668                                                 }
2669                                         }
2670                                 }
2671                         }
2672
2673                         else
2674                         {
2675                                 /* We should never get here, but if */
2676                                 /* we do, display the region as black */
2677
2678                                 fprintf(fd,"%c%c%c",0,0,0);
2679                         }
2680                 }
2681         }
2682
2683         /* Display legend along bottom of image */
2684
2685         x0=width/16;
2686
2687         for (y0=0; y0<30; y0++)
2688         {
2689                 for (indx=0; indx<16; indx++)
2690                 {
2691                         for (x=0; x<x0; x++)
2692                         {
2693                                 t=indx;  
2694                                 t2=indx+8;
2695
2696                                 if (y0>=10 && y0<=18)
2697                                 {  
2698                                         if (t2>9)
2699                                         {
2700                                                 if (x>=11 && x<=17)     
2701                                                         if (smallfont[t2/10][y0-10][x-11])
2702                                                                 t=255; 
2703                                         }
2704
2705                                         if (x>=19 && x<=25)     
2706                                                 if (smallfont[t2%10][y0-10][x-19])
2707                                                         t=255;
2708  
2709                                         if (x>=27 && x<=33)
2710                                                 if (smallfont[0][y0-10][x-27])
2711                                                         t=255; 
2712                                 }
2713
2714                                 switch (t)
2715                                 {
2716                                         case 0:
2717                                         fprintf(fd,"%c%c%c",255,0,0);
2718                                         break;
2719
2720                                         case 1:
2721                                         fprintf(fd,"%c%c%c",255,128,0);
2722                                         break;
2723
2724                                         case 2:
2725                                         fprintf(fd,"%c%c%c",255,165,0);
2726                                         break;
2727
2728                                         case 3:
2729                                         fprintf(fd,"%c%c%c",255,206,0);
2730                                         break;
2731
2732                                         case 4:
2733                                         fprintf(fd,"%c%c%c",255,255,0);
2734                                         break;
2735
2736                                         case 5:
2737                                         fprintf(fd,"%c%c%c",184,255,0);
2738                                         break;
2739
2740                                         case 6:
2741                                         fprintf(fd,"%c%c%c",0,255,0);
2742                                         break;
2743
2744                                         case 7:
2745                                         fprintf(fd,"%c%c%c",0,208,0);
2746                                         break;
2747
2748                                         case 8:
2749                                         fprintf(fd,"%c%c%c",0,196,196);
2750                                         break;
2751
2752                                         case 9:
2753                                         fprintf(fd,"%c%c%c",0,148,255);
2754                                         break;
2755
2756                                         case 10:
2757                                         fprintf(fd,"%c%c%c",80,80,255);
2758                                         break;
2759
2760                                         case 11:
2761                                         fprintf(fd,"%c%c%c",0,38,255);
2762                                         break;
2763
2764                                         case 12:
2765                                         fprintf(fd,"%c%c%c",142,63,255);
2766                                         break;
2767
2768                                         case 13:
2769                                         fprintf(fd,"%c%c%c",196,54,255);
2770                                         break;
2771
2772                                         case 14:
2773                                         fprintf(fd,"%c%c%c",255,0,255);
2774                                         break;
2775
2776                                         case 255:
2777                                         /* Black */
2778                                         fprintf(fd,"%c%c%c",0,0,0);
2779                                         break;
2780
2781                                         default:
2782                                         fprintf(fd,"%c%c%c",255,194,204);
2783                                 }
2784                         } 
2785                 }
2786         }
2787
2788         fclose(fd);
2789         fprintf(stdout,"Done!\n");
2790         fflush(stdout);
2791 }
2792
2793 void GraphTerrain(struct site source, struct site destination, char *name)
2794 {
2795         /* This function invokes gnuplot to generate an appropriate
2796            output file indicating the terrain profile between the source
2797            and destination locations.  "filename" is the name assigned
2798            to the output file generated by gnuplot.  The filename extension
2799            is used to set gnuplot's terminal setting and output file type.
2800            If no extension is found, .gif is assumed.  */
2801
2802         int x, y, z;
2803         char filename[255], term[15], ext[15];
2804         FILE *fd=NULL;
2805
2806         ReadPath(destination,source);
2807
2808         fd=fopen("profile.gp","wb");
2809
2810         for (x=0; x<path.length; x++)
2811                 fprintf(fd,"%f\t%f\n",path.distance[x],path.elevation[x]);
2812
2813         fclose(fd);
2814
2815         if (name[0]==0)
2816         {
2817                 /* Default filename and output file type */
2818
2819                 strncpy(filename,"profile\0",8);
2820                 strncpy(term,"gif\0",4);
2821                 strncpy(ext,"gif\0",4);
2822         }
2823
2824         else
2825         {
2826                 /* Grab extension and terminal type from "name" */
2827
2828                 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
2829                         filename[x]=name[x];
2830
2831                 if (name[x]=='.')
2832                 {
2833                         for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
2834                         {
2835                                 term[y]=tolower(name[x]);
2836                                 ext[y]=term[y];
2837                         }
2838
2839                         ext[y]=0;
2840                         term[y]=0;
2841                         filename[z]=0;
2842                 }
2843
2844                 else
2845                 {       /* No extension -- Default is gif */
2846
2847                         filename[x]=0;
2848                         strncpy(term,"gif\0",4);
2849                         strncpy(ext,"gif\0",4);
2850                 }
2851         }
2852
2853         /* Either .ps or .postscript may be used
2854            as an extension for postscript output. */
2855
2856         if (strncmp(term,"postscript",10)==0)
2857                 strncpy(ext,"ps\0",3);
2858
2859         else if (strncmp(ext,"ps",2)==0)
2860                 strncpy(term,"postscript\0",11);
2861
2862         fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
2863         fflush(stdout);
2864
2865         fd=fopen("splat.gp","w");
2866         fprintf(fd,"set grid\n");
2867         fprintf(fd,"set autoscale\n");
2868         fprintf(fd,"set term %s\n",term);
2869         fprintf(fd,"set title \"SPLAT! Terrain Profile\"\n");
2870         fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name);
2871         fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (feet)\"\n");
2872         fprintf(fd,"set output \"%s.%s\"\n",filename,ext);
2873         fprintf(fd,"plot \"profile.gp\" title \"\" with lines\n");
2874         fclose(fd);
2875                         
2876         x=system("gnuplot splat.gp");
2877
2878         if (x!=-1)
2879         {
2880                 unlink("splat.gp");
2881                 unlink("profile.gp");
2882                 fprintf(stdout," Done!\n");
2883                 fflush(stdout);
2884         }
2885
2886         else
2887                 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
2888 }
2889
2890 void GraphElevation(struct site source, struct site destination, char *name)
2891 {
2892         /* This function invokes gnuplot to generate an appropriate
2893            output file indicating the terrain profile between the source
2894            and destination locations.  "filename" is the name assigned
2895            to the output file generated by gnuplot.  The filename extension
2896            is used to set gnuplot's terminal setting and output file type.
2897            If no extension is found, .gif is assumed. */
2898
2899         int x, y, z;
2900         char filename[255], term[15], ext[15];
2901         double angle, refangle, maxangle=-90.0;
2902         struct site remote;
2903         FILE *fd=NULL, *fd2=NULL;
2904
2905         ReadPath(destination,source);  /* destination=RX, source=TX */
2906         refangle=ElevationAngle(destination,source);
2907
2908         fd=fopen("profile.gp","wb");
2909         fd2=fopen("reference.gp","wb");
2910
2911         for (x=1; x<path.length-1; x++)
2912         {
2913                 remote.lat=path.lat[x];
2914                 remote.lon=path.lon[x];
2915                 remote.alt=0.0;
2916                 angle=ElevationAngle(destination,remote);
2917                 fprintf(fd,"%f\t%f\n",path.distance[x],angle);
2918                 fprintf(fd2,"%f\t%f\n",path.distance[x],refangle);
2919
2920                 if (angle>maxangle)
2921                         maxangle=angle;
2922         }
2923
2924         fprintf(fd,"%f\t%f\n",path.distance[path.length-1],refangle);
2925         fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],refangle);
2926
2927         fclose(fd);
2928         fclose(fd2);
2929
2930         if (name[0]==0)
2931         {
2932                 /* Default filename and output file type */
2933
2934                 strncpy(filename,"profile\0",8);
2935                 strncpy(term,"gif\0",4);
2936                 strncpy(ext,"gif\0",4);
2937         }
2938
2939         else
2940         {
2941                 /* Grab extension and terminal type from "name" */
2942
2943                 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
2944                         filename[x]=name[x];
2945
2946                 if (name[x]=='.')
2947                 {
2948                         for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
2949                         {
2950                                 term[y]=tolower(name[x]);
2951                                 ext[y]=term[y];
2952                         }
2953
2954                         ext[y]=0;
2955                         term[y]=0;
2956                         filename[z]=0;
2957                 }
2958
2959                 else
2960                 {       /* No extension -- Default is gif */
2961
2962                         filename[x]=0;
2963                         strncpy(term,"gif\0",4);
2964                         strncpy(ext,"gif\0",4);
2965                 }
2966         }
2967
2968         /* Either .ps or .postscript may be used
2969            as an extension for postscript output. */
2970
2971         if (strncmp(term,"postscript",10)==0)
2972                 strncpy(ext,"ps\0",3);
2973
2974         else if (strncmp(ext,"ps",2)==0)
2975                 strncpy(term,"postscript\0",11);
2976
2977         fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
2978         fflush(stdout);
2979
2980         fd=fopen("splat.gp","w");
2981
2982         fprintf(fd,"set grid\n");
2983         fprintf(fd,"set yrange [%2.3f to %2.3f]\n", (-fabs(refangle)-0.25), maxangle+0.25);
2984         fprintf(fd,"set term %s\n",term);
2985         fprintf(fd,"set title \"SPLAT! Elevation Profile\"\n");
2986         fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name);
2987         fprintf(fd,"set ylabel \"Elevation Angle Along Path Between %s and %s (degrees)\"\n",destination.name,source.name);
2988         fprintf(fd,"set output \"%s.%s\"\n",filename,ext);
2989         fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines\n");
2990
2991         fclose(fd);
2992                         
2993         x=system("gnuplot splat.gp");
2994
2995         if (x!=-1)
2996         {
2997                 unlink("splat.gp");
2998                 unlink("profile.gp");
2999                 unlink("reference.gp"); 
3000
3001                 fprintf(stdout," Done!\n");
3002                 fflush(stdout);
3003         }
3004
3005         else
3006                 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
3007 }
3008
3009 void GraphHeight(struct site source, struct site destination, char *name)
3010 {
3011         /* This function invokes gnuplot to generate an appropriate
3012            output file indicating the terrain profile between the source
3013            and destination locations.  What is plotted is the height of
3014            land above or below a straight line between the receibe and
3015            transmit sites.  "filename" is the name assigned to the output
3016            file generated by gnuplot.  The filename extension is used
3017            to set gnuplot's terminal setting and output file type.
3018            If no extension is found, .gif is assumed. */
3019
3020         int x, y, z;
3021         char filename[255], term[15], ext[15];
3022         double a, b, c, height, refangle, cangle, maxheight=-100000.0,
3023                minheight=100000.0;
3024         struct site remote;
3025         FILE *fd=NULL, *fd2=NULL;
3026
3027         ReadPath(destination,source);  /* destination=RX, source=TX */
3028         refangle=ElevationAngle(destination,source);
3029         b=GetElevation(destination)+destination.alt+earthradius;
3030
3031         fd=fopen("profile.gp","wb");
3032         fd2=fopen("reference.gp","wb");
3033
3034         for (x=1; x<path.length-1; x++)
3035         {
3036                 remote.lat=path.lat[x];
3037                 remote.lon=path.lon[x];
3038                 remote.alt=0.0;
3039
3040                 a=GetElevation(remote)+earthradius;
3041
3042                 cangle=5280.0*Distance(destination,remote)/earthradius;
3043
3044                 c=b*sin(refangle*deg2rad+HALFPI)/sin(HALFPI-refangle*deg2rad-cangle);
3045
3046                 height=a-c;
3047
3048                 fprintf(fd,"%f\t%f\n",path.distance[x],height);
3049                 fprintf(fd2,"%f\t%f\n",path.distance[x],0.);
3050
3051                 if (height>maxheight)
3052                         maxheight=height;
3053
3054                 if (height<minheight)
3055                         minheight=height;
3056         }
3057
3058         fprintf(fd,"%f\t%f\n",path.distance[path.length-1],0.0);
3059         fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],0.0);
3060
3061         fclose(fd);
3062         fclose(fd2);
3063
3064         if (name[0]==0)
3065         {
3066                 /* Default filename and output file type */
3067
3068                 strncpy(filename,"height\0",8);
3069                 strncpy(term,"gif\0",4);
3070                 strncpy(ext,"gif\0",4);
3071         }
3072
3073         else
3074         {
3075                 /* Grab extension and terminal type from "name" */
3076
3077                 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
3078                         filename[x]=name[x];
3079
3080                 if (name[x]=='.')
3081                 {
3082                         for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
3083                         {
3084                                 term[y]=tolower(name[x]);
3085                                 ext[y]=term[y];
3086                         }
3087
3088                         ext[y]=0;
3089                         term[y]=0;
3090                         filename[z]=0;
3091                 }
3092
3093                 else
3094                 {       /* No extension -- Default is gif */
3095
3096                         filename[x]=0;
3097                         strncpy(term,"gif\0",4);
3098                         strncpy(ext,"gif\0",4);
3099                 }
3100         }
3101
3102         /* Either .ps or .postscript may be used
3103            as an extension for postscript output. */
3104
3105         if (strncmp(term,"postscript",10)==0)
3106                 strncpy(ext,"ps\0",3);
3107
3108         else if (strncmp(ext,"ps",2)==0)
3109                 strncpy(term,"postscript\0",11);
3110
3111         fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
3112         fflush(stdout);
3113
3114         fd=fopen("splat.gp","w");
3115
3116         minheight-=20.0;
3117         maxheight+=20.0;
3118
3119         if (maxheight<20.0)
3120                 maxheight=20.0;
3121
3122         fprintf(fd,"set grid\n");
3123         fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minheight, maxheight);
3124         fprintf(fd,"set term %s\n",term);
3125         fprintf(fd,"set title \"SPLAT! Height Profile\"\n");
3126         fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name);
3127         fprintf(fd,"set ylabel \"Ground Height Above Path Between %s and %s (feet)\"\n",destination.name,source.name);
3128         fprintf(fd,"set output \"%s.%s\"\n",filename,ext);
3129         fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines\n");
3130
3131         fclose(fd);
3132
3133         x=system("gnuplot splat.gp");
3134
3135         if (x!=-1)
3136         {
3137                 unlink("splat.gp");
3138                 unlink("profile.gp");
3139                 unlink("reference.gp"); 
3140                 fprintf(stdout," Done!\n");
3141                 fflush(stdout);
3142         }
3143
3144         else
3145                 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
3146 }
3147
3148 void GraphLongley(struct site source, struct site destination, char *name)
3149 {
3150         /* This function invokes gnuplot to generate an appropriate
3151            output file indicating the Longley-Rice model loss between
3152            the source and destination locations.   "filename" is the
3153            name assigned to the output file generated by gnuplot.
3154            The filename extension is used to set gnuplot's terminal
3155            setting and output file type.  If no extension is found,
3156            .gif is assumed.  */
3157
3158         int x, y, z, errnum, errflag=0;
3159         char filename[255], term[15], ext[15], strmode[100], report_name[80];
3160         double maxloss=-100000.0, minloss=100000.0, loss, haavt, angle;
3161         FILE *fd=NULL, *fd2=NULL;
3162
3163         sprintf(report_name,"%s-to-%s.lro",source.name,destination.name);
3164
3165         for (x=0; report_name[x]!=0; x++)
3166                 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
3167                         report_name[x]='_';     
3168
3169         fd2=fopen(report_name,"w");
3170
3171         fprintf(fd2,"\n\t--==[ SPLAT! v%s Longley-Rice Model Path Loss Report ]==--\n\n",splat_version);
3172         fprintf(fd2,"Analysis of RF path conditions between %s and %s:\n",source.name, destination.name);
3173         fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
3174         fprintf(fd2,"Transmitter site: %s\n",source.name);
3175
3176         if (source.lat>=0.0)
3177         {
3178                 fprintf(fd2,"Site location: %.4f North / %.4f West",source.lat, source.lon);
3179                 fprintf(fd2, " (%s N / ", dec2dms(source.lat));
3180         }
3181
3182         else
3183         {
3184
3185                 fprintf(fd2,"Site location: %.4f South / %.4f West",-source.lat, source.lon);
3186                 fprintf(fd2, " (%s S / ", dec2dms(source.lat));
3187         }
3188         
3189         fprintf(fd2, "%s W)\n", dec2dms(source.lon));
3190         fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(source));
3191         fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",source.alt, source.alt+GetElevation(source));
3192
3193         haavt=haat(source);
3194
3195         if (haavt>-4999.0)
3196                 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
3197
3198         fprintf(fd2,"Distance to %s: %.2f miles.\n",destination.name,Distance(source,destination));
3199         fprintf(fd2,"Azimuth to %s: %.2f degrees.\n",destination.name,Azimuth(source,destination));
3200
3201         angle=ElevationAngle(source,destination);
3202
3203         if (angle>=0.0)
3204                 fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle);
3205
3206         if (angle<0.0)
3207                 fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle);
3208
3209         fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
3210
3211         /* Receiver */
3212
3213         fprintf(fd2,"Receiver site: %s\n",destination.name);
3214
3215         if (destination.lat>=0.0)
3216         {
3217                 fprintf(fd2,"Site location: %.4f North / %.4f West",destination.lat, destination.lon);
3218                 fprintf(fd2, " (%s N / ", dec2dms(destination.lat));
3219         }
3220
3221         else
3222         {
3223                 fprintf(fd2,"Site location: %.4f South / %.4f West",-destination.lat, destination.lon);
3224                 fprintf(fd2, " (%s S / ", dec2dms(destination.lat));
3225         }
3226
3227         fprintf(fd2, "%s W)\n", dec2dms(destination.lon));
3228         fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(destination));
3229         fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",destination.alt, destination.alt+GetElevation(destination));
3230
3231         haavt=haat(destination);
3232
3233         if (haavt>-4999.0)
3234                 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
3235
3236         fprintf(fd2,"Distance to %s: %.2f miles.\n",source.name,Distance(source,destination));
3237         fprintf(fd2,"Azimuth to %s: %.2f degrees.\n",source.name,Azimuth(destination,source));
3238
3239         angle=ElevationAngle(destination,source);
3240
3241         if (angle>=0.0)
3242                 fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle);
3243
3244         if (angle<0.0)
3245                 fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle);
3246
3247         fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
3248
3249         fprintf(fd2,"Longley-Rice path calculation parameters used in this analysis:\n\n");
3250         fprintf(fd2,"Earth's Dielectric Constant: %.3lf\n",LR.eps_dielect);
3251         fprintf(fd2,"Earth's Conductivity: %.3lf\n",LR.sgm_conductivity);
3252         fprintf(fd2,"Atmospheric Bending Constant (N): %.3lf\n",LR.eno_ns_surfref);
3253         fprintf(fd2,"Frequency: %.3lf (MHz)\n",LR.frq_mhz);
3254         fprintf(fd2,"Radio Climate: %d (",LR.radio_climate);
3255
3256         switch (LR.radio_climate)
3257         {
3258                 case 1:
3259                 fprintf(fd2,"Equatorial");
3260                 break;
3261
3262                 case 2:
3263                 fprintf(fd2,"Continental Subtropical");
3264                 break;
3265
3266                 case 3:
3267                 fprintf(fd2,"Maritime Subtropical");
3268                 break;
3269
3270                 case 4:
3271                 fprintf(fd2,"Desert");
3272                 break;
3273
3274                 case 5:
3275                 fprintf(fd2,"Continental Temperate");
3276                 break;
3277
3278                 case 6:
3279                 fprintf(fd2,"Martitime Temperate, Over Land");
3280                 break;
3281
3282                 case 7:
3283                 fprintf(fd2,"Maritime Temperate, Over Sea");
3284                 break;
3285
3286                 default:
3287                 fprintf(fd2,"Unknown");
3288         }
3289
3290         fprintf(fd2,")\nPolarization: %d (",LR.pol);
3291
3292         if (LR.pol==0)
3293                 fprintf(fd2,"Horizontal");
3294
3295         if (LR.pol==1)
3296                 fprintf(fd2,"Vertical");
3297
3298         fprintf(fd2,")\nFraction of Situations: %.1lf%c\n",LR.conf*100.0,37);
3299         fprintf(fd2,"Fraction of Time: %.1lf%c\n",LR.rel*100.0,37);
3300
3301         fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
3302
3303         fprintf(fd2,"Analysis Results:\n\n");
3304
3305         ReadPath(source, destination);  /* destination=RX, source=TX */
3306
3307         elev_l[1]=0.04*METERS_PER_MILE;
3308
3309         for (x=1; x<path.length; x++)
3310                 elev_l[x+1]=path.elevation[x]*METERS_PER_FOOT;  
3311
3312         fprintf(fd2,"Distance (mi)\tLoss (dB)\tErrnum\tComment\n\n"); 
3313
3314         fd=fopen("profile.gp","w");
3315
3316         for (x=2; x<path.length; x++)
3317         {
3318                 elev_l[0]=x-1;
3319
3320                 point_to_point(elev_l, source.alt*METERS_PER_FOOT, 
3321                         destination.alt*METERS_PER_FOOT,
3322                         LR.eps_dielect, LR.sgm_conductivity, LR.eno_ns_surfref,
3323                         LR.frq_mhz, LR.radio_climate, LR.pol, LR.conf, LR.rel,
3324                         loss, strmode, errnum);
3325
3326                         /* Note: PASS BY REFERENCE ... loss and errnum are
3327                            passed by reference, and are only used in this
3328                            file by this function */
3329
3330
3331                 fprintf(fd,"%f\t%f\n",path.distance[path.length-1]-path.distance[x],loss);
3332                 fprintf(fd2,"%7.2f\t\t%7.2f\t\t  %d\t%s\n",path.distance[x],loss, errnum, strmode);
3333
3334                 errflag|=errnum;
3335                   
3336                 if (loss>maxloss)
3337                         maxloss=loss;
3338
3339                 if (loss<minloss)
3340                         minloss=loss;
3341         }
3342
3343         fclose(fd);
3344
3345         if (errflag)
3346         {
3347                 fprintf(fd2,"\nNotes on \"errnum\"...\n\n");
3348                 fprintf(fd2,"  0: No error.  :-)\n");
3349                 fprintf(fd2,"  1: Warning!  Some parameters are nearly out of range.\n");
3350                 fprintf(fd2,"     Results should be used with caution.\n");
3351                 fprintf(fd2,"  2: Note: Default parameters have been substituted for impossible ones.\n");
3352                 fprintf(fd2,"  3: Warning!  A combination of parameters is out of range.\n");
3353                 fprintf(fd2,"     Results are probably invalid.\n");
3354                 fprintf(fd2,"  Other: Warning!  Some parameters are out of range.\n");
3355                 fprintf(fd2,"    Results are probably invalid.\n\nEnd of Report\n");
3356         }
3357
3358         fprintf(stdout,"Longley-Rice Path Loss between %s and %s is %.2f db\n",source.name, destination.name, loss);
3359         fprintf(stdout,"Path Loss Report written to: \"%s\"\n",report_name);
3360         fflush(stdout);
3361
3362         fclose(fd2);
3363
3364         if (name[0]==0)
3365         {
3366                 /* Default filename and output file type */
3367
3368                 strncpy(filename,"loss\0",5);
3369                 strncpy(term,"gif\0",4);
3370                 strncpy(ext,"gif\0",4);
3371         }
3372
3373         else
3374         {
3375                 /* Grab extension and terminal type from "name" */
3376
3377                 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
3378                         filename[x]=name[x];
3379
3380                 if (name[x]=='.')
3381                 {
3382                         for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
3383                         {
3384                                 term[y]=tolower(name[x]);
3385                                 ext[y]=term[y];
3386                         }
3387
3388                         ext[y]=0;
3389                         term[y]=0;
3390                         filename[z]=0;
3391                 }
3392
3393                 else
3394                 {       /* No extension -- Default is gif */
3395
3396                         filename[x]=0;
3397                         strncpy(term,"gif\0",4);
3398                         strncpy(ext,"gif\0",4);
3399                 }
3400         }
3401
3402         /* Either .ps or .postscript may be used
3403            as an extension for postscript output. */
3404
3405         if (strncmp(term,"postscript",10)==0)
3406                 strncpy(ext,"ps\0",3);
3407
3408         else if (strncmp(ext,"ps",2)==0)
3409                 strncpy(term,"postscript\0",11);
3410
3411         fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
3412         fflush(stdout);
3413
3414         fd=fopen("splat.gp","w");
3415
3416         fprintf(fd,"set grid\n");
3417         fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minloss, maxloss);
3418         fprintf(fd,"set term %s\n",term);
3419         fprintf(fd,"set title \"SPLAT! Loss Profile\"\n");
3420         fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name);
3421         fprintf(fd,"set ylabel \"Longley-Rice Loss (dB)\"\n");
3422         fprintf(fd,"set output \"%s.%s\"\n",filename,ext);
3423         fprintf(fd,"plot \"profile.gp\" title \"Longley-Rice Loss\" with lines\n");
3424
3425         fclose(fd);
3426                         
3427         x=system("gnuplot splat.gp");
3428
3429         if (x!=-1)
3430         {
3431                 unlink("splat.gp");
3432                 unlink("profile.gp");
3433                 unlink("reference.gp"); 
3434
3435                 fprintf(stdout," Done!\n");
3436                 fflush(stdout);
3437         }
3438
3439         else
3440                 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
3441 }
3442
3443 void ObstructionReport(struct site xmtr, struct site rcvr, char report)
3444 {
3445         struct site result, result2, new_site;
3446         double angle, haavt;
3447         unsigned char block;
3448         char report_name[80], string[255];
3449         int x;
3450         FILE *fd;
3451
3452         sprintf(report_name,"%s-to-%s.txt",xmtr.name,rcvr.name);
3453
3454         for (x=0; report_name[x]!=0; x++)
3455                 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
3456                         report_name[x]='_';     
3457
3458         fd=fopen(report_name,"w");
3459
3460         fprintf(fd,"\n\t\t--==[ SPLAT! v%s Obstruction Report ]==--\n\n",splat_version);
3461         fprintf(fd,"Analysis of line-of-sight path conditions between %s and %s:\n",xmtr.name, rcvr.name);
3462         fprintf(fd,"\n-------------------------------------------------------------------------\n\n");
3463         fprintf(fd,"Transmitter site: %s\n",xmtr.name);
3464
3465
3466         if (xmtr.lat>=0.0)
3467         {
3468                 fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon);
3469                 fprintf(fd, " (%s N / ", dec2dms(xmtr.lat));
3470         }
3471
3472         else
3473         {
3474                 fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon);
3475                 fprintf(fd, " (%s S / ", dec2dms(xmtr.lat));
3476         }
3477
3478         fprintf(fd, "%s W)\n", dec2dms(xmtr.lon));
3479         fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr));
3480         fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr));
3481
3482         haavt=haat(xmtr);
3483
3484         if (haavt>-4999.0)
3485                 fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt);
3486
3487         fprintf(fd,"Distance to %s: %.2f miles.\n",rcvr.name,Distance(xmtr,rcvr));
3488         fprintf(fd,"Azimuth to %s: %.2f degrees.\n",rcvr.name,Azimuth(xmtr,rcvr));
3489
3490         angle=ElevationAngle(xmtr,rcvr);
3491
3492         if (angle>=0.0)
3493                 fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle);
3494
3495         if (angle<0.0)
3496                 fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle);
3497
3498         fprintf(fd,"\n-------------------------------------------------------------------------\n\n");
3499
3500         /* Receiver */
3501
3502         fprintf(fd,"Receiver site: %s\n",rcvr.name);
3503
3504         if (rcvr.lat>=0.0)
3505         {
3506                 fprintf(fd,"Site location: %.4f North / %.4f West",rcvr.lat, rcvr.lon);
3507                 fprintf(fd, " (%s N / ", dec2dms(rcvr.lat));
3508         }
3509
3510         else
3511         {
3512                 fprintf(fd,"Site location: %.4f South / %.4f West",-rcvr.lat, rcvr.lon);
3513                 fprintf(fd, " (%s S / ", dec2dms(rcvr.lat));
3514         }
3515
3516         fprintf(fd, "%s W)\n", dec2dms(rcvr.lon));
3517         fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(rcvr));
3518         fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",rcvr.alt, rcvr.alt+GetElevation(rcvr));
3519
3520         haavt=haat(rcvr);
3521
3522         if (haavt>-4999.0)
3523                 fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt);
3524
3525         fprintf(fd,"Distance to %s: %.2f miles.\n",xmtr.name,Distance(xmtr,rcvr));
3526         fprintf(fd,"Azimuth to %s: %.2f degrees.\n",xmtr.name,Azimuth(rcvr,xmtr));
3527
3528         angle=ElevationAngle(rcvr,xmtr);
3529
3530         if (angle>=0.0)
3531                 fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle);
3532
3533         if (angle<0.0)
3534                 fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle);
3535
3536         fprintf(fd,"\n-------------------------------------------------------------------------\n\n");
3537
3538         if (report=='y')
3539         {
3540                 /* Write an Obstruction Report */
3541
3542                 new_site=rcvr;
3543                 result=los(xmtr,rcvr);
3544                 result2=result;
3545                 result2.alt-=1;
3546                 block=result.name[0];
3547
3548                 if (block=='*')
3549                         fprintf(fd,"SPLAT! detected obstructions at:\n\n");
3550
3551                 while (block=='*')
3552                 {
3553                         if (result.lat!=result2.lat || result.lon!=result2.lon || result.alt!=result2.alt)
3554                         {
3555                                 if (result.lat>=0.0)
3556                                         fprintf(fd,"\t%.4f N, %.4f W, %5.2f miles, %6.2f feet AMSL.\n",result.lat, result.lon, Distance(rcvr,result), result.alt);
3557                                 else
3558
3559                                         fprintf(fd,"\t%.4f S, %.4f W, %5.2f miles, %6.2f feet AMSL.\n",-result.lat, result.lon, Distance(rcvr,result), result.alt);
3560                         }
3561
3562                         result2=result;
3563                         new_site.alt+=1.0;
3564
3565                         /* Can you hear me now? :-) */
3566
3567                         result=los(xmtr,new_site);
3568                         block=result.name[0];
3569                 }
3570
3571                 if (new_site.alt!=rcvr.alt)
3572                         sprintf(string,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear all obstructions detected by SPLAT!\n\n",rcvr.name, new_site.alt);
3573                 else
3574                         sprintf(string,"\nNo obstructions due to terrain were detected by SPLAT!\n\n");
3575         }
3576
3577         fprintf(fd,"%s",string);
3578
3579         fclose(fd);
3580
3581         /* Display LOS status to terminal */
3582
3583         fprintf(stdout,"%sObstruction report written to: \"%s\"\n",string,report_name);
3584         fflush(stdout);
3585 }
3586
3587 void SiteReport(struct site xmtr)
3588 {
3589         char report_name[80];
3590         double terrain;
3591         int x, azi;
3592         FILE *fd;
3593
3594         sprintf(report_name,"%s-site_report.txt",xmtr.name);
3595
3596         for (x=0; report_name[x]!=0; x++)
3597                 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
3598                         report_name[x]='_';     
3599
3600         fd=fopen(report_name,"w");
3601
3602         fprintf(fd,"\n\t--==[ SPLAT! v%s Site Analysis Report For: %s ]==--\n\n",splat_version,xmtr.name);
3603
3604         fprintf(fd,"---------------------------------------------------------------------------\n\n");
3605
3606         if (xmtr.lat>=0.0)
3607         {
3608                 fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon);
3609                 fprintf(fd, " (%s N / ",dec2dms(xmtr.lat));
3610         }
3611
3612         else
3613         {
3614                 fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon);
3615                 fprintf(fd, " (%s S / ",dec2dms(xmtr.lat));
3616         }
3617
3618         fprintf(fd, "%s W)\n",dec2dms(xmtr.lon));
3619         fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr));
3620         fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr));
3621
3622         terrain=haat(xmtr);
3623
3624         if (terrain>-4999.0)
3625         {
3626                 fprintf(fd,"Antenna height above average terrain: %.2f feet\n\n",terrain);
3627
3628                 /* Display the average terrain between 2 and 10 miles
3629                    from the transmitter site at azimuths of 0, 45, 90,
3630                    135, 180, 225, 270, and 315 degrees. */
3631
3632                 for (azi=0; azi<=315; azi+=45)
3633                 {
3634                         fprintf(fd,"Average terrain at %3d degrees azimuth: ",azi);
3635                         terrain=AverageTerrain(xmtr,(double)azi,2.0,10.0);
3636
3637                         if (terrain>-4999.0)
3638                                 fprintf(fd,"%.2f feet AMSL\n",terrain);
3639                         else
3640                                 fprintf(fd,"No terrain\n");
3641                 }
3642         }
3643
3644         fprintf(fd,"\n---------------------------------------------------------------------------\n\n");
3645         fclose(fd);
3646         fprintf(stdout,"\nSite analysis report written to: \"%s\"\n",report_name);
3647 }
3648
3649 int main(char argc, char *argv[])
3650 {
3651         int             x, y, ymin, ymax, width, z=0, min_lat, min_lon,
3652                         max_lat, max_lon, rxlat, rxlon, txlat, txlon,
3653                         west_min, west_max, north_min, north_max;
3654
3655         unsigned char   coverage=0, LRmap=0, ext[20], terrain_plot=0,
3656                         elevation_plot=0, height_plot=0, 
3657                         longley_plot=0, cities=0, bfs=0, txsites=0,
3658                         count, report='y';
3659  
3660         char            mapfile[255], header[80], city_file[5][255], 
3661                         elevation_file[255], height_file[255], 
3662                         longley_file[255], terrain_file[255],
3663                         string[255], rxfile[255], *env=NULL,
3664                         txfile[255], map=0, boundary_file[5][255],
3665                         rxsite=0;
3666
3667         double          altitude=0.0, altitudeLR=0.0, tx_range=0.0,
3668                         rx_range=0.0, deg_range=0.0, deg_limit,
3669                         deg_range_lon, er_mult;
3670
3671         struct          site tx_site[4], rx_site;
3672
3673         FILE            *fd;
3674
3675         sprintf(header,"\n\t\t--==[ Welcome To SPLAT! v%s ]==--\n\n", splat_version);
3676
3677         if (argc==1)
3678         {
3679                 fprintf(stdout, "%sAvailable Options...\n\n\t -t txsite(s).qth (max of 4)\n\t -r rxsite.qth\n",header);
3680                 fprintf(stdout,"\t -c plot coverage area(s) of TX(s) based on an RX antenna at X feet AGL\n");
3681                 fprintf(stdout,"\t -L plot path loss map of TX based on an RX antenna at X feet AGL\n");
3682                 fprintf(stdout,"\t -s filename(s) of city/site file(s) to import (max of 5)\n");
3683                 fprintf(stdout,"\t -b filename(s) of cartographic boundary file(s) to import (max of 5)\n");
3684                 fprintf(stdout,"\t -p filename of terrain profile graph to plot\n");
3685                 fprintf(stdout,"\t -e filename of terrain elevation graph to plot\n");
3686                 fprintf(stdout,"\t -h filename of terrain height graph to plot\n");
3687                 fprintf(stdout,"\t -l filename of Longley-Rice graph to plot\n");
3688                 fprintf(stdout,"\t -o filename of topographic map to generate (.ppm)\n");
3689                 fprintf(stdout,"\t -d sdf file directory path (overrides path in ~/.splat_path file)\n");
3690                 fprintf(stdout,"\t -n no analysis, brief report\n\t -N no analysis, no report\n");
3691                 fprintf(stdout,"\t -m earth radius multiplier\n");
3692                 fprintf(stdout,"\t -R modify default range for -c or -L (miles)\n");
3693                 fprintf(stdout,"\t-db maximum loss contour to display on path loss maps (80-230 dB)\n\n");
3694
3695                 fprintf(stdout,"Type 'man splat', or see the documentation for more details.\n\n");
3696                 fflush(stdout);
3697                 return 1;
3698         }
3699
3700         y=argc-1;
3701
3702         rxfile[0]=0;
3703         txfile[0]=0;
3704         string[0]=0;
3705         mapfile[0]=0;
3706         elevation_file[0]=0;
3707         terrain_file[0]=0;
3708         sdf_path[0]=0;
3709         path.length=0;
3710         rx_site.lat=91.0;
3711         rx_site.lon=361.0;
3712         earthradius=EARTHRADIUS;
3713
3714         for (x=0; x<4; x++)
3715         {
3716                 tx_site[x].lat=91.0;
3717                 tx_site[x].lon=361.0;
3718         }
3719
3720         for (x=0; x<MAXSLOTS; x++)
3721         {
3722                 dem[x].min_el=32768;
3723                 dem[x].max_el=-32768;
3724                 dem[x].min_north=90;
3725                 dem[x].max_north=-90;
3726                 dem[x].min_west=360;
3727                 dem[x].max_west=-1;
3728         }
3729
3730
3731         /* Scan for command line arguments */
3732
3733         for (x=1; x<=y; x++)
3734         {
3735                 if (strcmp(argv[x],"-R")==0)
3736                 {
3737                         z=x+1;
3738
3739                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3740                         {
3741                                 sscanf(argv[z],"%lf",&max_range);
3742
3743                                 if (max_range<0.0)
3744                                         max_range=0.0;
3745
3746                                 if (max_range>1000.0)
3747                                         max_range=1000.0;
3748                         }                        
3749                 }
3750
3751                 if (strcmp(argv[x],"-m")==0)
3752                 {
3753                         z=x+1;
3754
3755                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3756                         {
3757                                 sscanf(argv[z],"%lf",&er_mult);
3758
3759                                 if (er_mult<0.1)
3760                                         er_mult=1.0;
3761
3762                                 if (er_mult>1.0e6)
3763                                         er_mult=1.0e6;
3764
3765                                 earthradius*=er_mult;
3766                         }                        
3767                 }
3768
3769                 if (strcmp(argv[x],"-o")==0)
3770                 {
3771                         z=x+1;
3772
3773                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3774                                 strncpy(mapfile,argv[z],253);
3775                         map=1;
3776                 }
3777
3778                 if (strcmp(argv[x],"-c")==0)
3779                 {
3780                         z=x+1;
3781
3782                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3783                         {
3784                                 sscanf(argv[z],"%lf",&altitude);
3785                                 coverage=1;
3786                         }
3787                 }
3788
3789                 if (strcmp(argv[x],"-db")==0)
3790                 {
3791                         z=x+1;
3792
3793                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3794                         {
3795                                 sscanf(argv[z],"%d",&maxdB);
3796
3797                                 maxdB=abs(maxdB);
3798
3799                                 if (maxdB<80)
3800                                         maxdB=80;
3801
3802                                 if (maxdB>230)
3803                                         maxdB=230;
3804                         }                        
3805                 }
3806
3807                 if (strcmp(argv[x],"-p")==0)
3808                 { 
3809                         z=x+1;
3810
3811                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3812                         {
3813                                 strncpy(terrain_file,argv[z],253);
3814                                 terrain_plot=1;
3815                         }
3816                 }
3817
3818                 if (strcmp(argv[x],"-e")==0)
3819                 {
3820                         z=x+1;
3821
3822                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3823                         {
3824                                 strncpy(elevation_file,argv[z],253);
3825                                 elevation_plot=1;
3826                         }
3827                 }
3828
3829                 if (strcmp(argv[x],"-h")==0)
3830                 {
3831                         z=x+1;
3832
3833                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3834                         {
3835                                 strncpy(height_file,argv[z],253);
3836                                 height_plot=1;
3837                         }
3838                 }
3839
3840                 if (strcmp(argv[x],"-n")==0)
3841                 {
3842                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3843                         {
3844                                 report='n';
3845                                 map=1;
3846                         }
3847                 }
3848
3849                 if (strcmp(argv[x],"-N")==0)
3850                 {
3851                         if (z<=y && argv[z][0] && argv[z][0]!='-');
3852                         {
3853                                 report='N';
3854                                 map=1;
3855                         }
3856                 }
3857
3858                 if (strcmp(argv[x],"-d")==0)
3859                 {
3860                         z=x+1;
3861
3862                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3863                                 strncpy(sdf_path,argv[z],253);
3864                 }
3865
3866                 if (strcmp(argv[x],"-t")==0)
3867                 {
3868                         /* Read Transmitter Location */
3869
3870                         z=x+1;
3871
3872                         while (z<=y && argv[z][0] && argv[z][0]!='-' && txsites<4)
3873                         {
3874                                 strncpy(txfile,argv[z],253);
3875                                 tx_site[txsites]=LoadQTH(txfile);
3876                                 txsites++;
3877                                 z++;
3878                         }
3879                         z--;
3880                 }
3881
3882                 if (strcmp(argv[x],"-L")==0)
3883                 {
3884                         z=x+1;
3885
3886                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3887                         {
3888                                 sscanf(argv[z],"%lf",&altitudeLR);
3889
3890                                 if (coverage)
3891                                         fprintf(stdout,"c and L are exclusive options, ignoring L.\n");
3892                                 else
3893                                 {
3894                                         LRmap=1;
3895                                         ReadLRParm(txfile);
3896                                 } 
3897                         }
3898                 }
3899
3900                 if (strcmp(argv[x],"-l")==0)
3901                 {
3902                         z=x+1;
3903
3904                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3905                         {
3906                                 strncpy(longley_file,argv[z],253);
3907                                 longley_plot=1;
3908                                 /* Doing this twice is harmless */
3909                                 ReadLRParm(txfile);
3910                         }
3911                 }
3912
3913                 if (strcmp(argv[x],"-r")==0)
3914                 {
3915                         /* Read Receiver Location */
3916
3917                         z=x+1;
3918
3919                         if (z<=y && argv[z][0] && argv[z][0]!='-')
3920                         {
3921                                 strncpy(rxfile,argv[z],253);
3922                                 rx_site=LoadQTH(rxfile);
3923                                 rxsite=1;
3924                         }
3925                 }
3926
3927                 if (strcmp(argv[x],"-s")==0)
3928                 {
3929                         /* Read city file(s) */
3930
3931                         z=x+1;
3932
3933                         while (z<=y && argv[z][0] && argv[z][0]!='-' && cities<5)
3934                         {
3935                                 strncpy(city_file[cities],argv[z],253);
3936                                 cities++;
3937                                 z++;
3938                         }
3939                         z--;
3940                 }
3941
3942                 if (strcmp(argv[x],"-b")==0)
3943                 {
3944                         /* Read Boundary File(s) */
3945
3946                         z=x+1;
3947
3948                         while (z<=y && argv[z][0] && argv[z][0]!='-' && bfs<5)
3949                         {
3950                                 strncpy(boundary_file[bfs],argv[z],253);
3951                                 bfs++;
3952                                 z++;
3953                         }
3954                         z--;
3955                 }
3956         }
3957
3958         /* Perform some error checking on the arguments
3959            and switches parsed from the command-line.
3960            If an error is encountered, print a message
3961            and exit gracefully. */
3962
3963         if (txsites==0)
3964         {
3965                 fprintf(stderr,"\n%c*** ERROR: No transmitter site(s) specified!\n\n",7);
3966                 exit (-1);
3967         }
3968
3969         for (x=0, y=0; x<txsites; x++)
3970         {
3971                 if (tx_site[x].lat==91.0 && tx_site[x].lon==361.0)
3972                 {
3973                         fprintf(stderr,"\n*** ERROR: Transmitter site #%d not found!",x+1);
3974                         y++;
3975                 }
3976         }
3977
3978         if (y)
3979         {
3980                 fprintf(stderr,"%c\n\n",7);
3981                 exit (-1);
3982         }
3983
3984         if ((coverage+LRmap)==0 && rx_site.lat==91.0 && rx_site.lon==361.0)
3985         {
3986                 fprintf(stderr,"\n%c*** ERROR: No receiver site found or specified!\n\n",7);
3987                 exit (-1);
3988         }
3989
3990         /* No errors were detected.  Whew!  :-) */
3991
3992         /* If no SDF path was specified on the command line (-d), check
3993            for a path specified in the $HOME/.splat_path file.  If the
3994            file is not found, then sdf_path[] remains NULL, and the
3995            current working directory is assumed to contain the SDF
3996            files. */
3997
3998         if (sdf_path[0]==0)
3999         {
4000                 env=getenv("HOME");
4001                 sprintf(string,"%s/.splat_path",env);
4002                 fd=fopen(string,"r");
4003
4004                 if (fd!=NULL)
4005                 {
4006                         fgets(string,253,fd);
4007
4008                         /* Remove <CR> and/or <LF> from string */
4009
4010                         for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0 && x<253; x++);
4011                         string[x]=0;
4012
4013                         strncpy(sdf_path,string,253);
4014
4015                         fclose(fd);
4016                 }
4017         }
4018
4019         /* Ensure a trailing '/' is present in sdf_path */
4020
4021         if (sdf_path[0])
4022         {
4023                 x=strlen(sdf_path);
4024
4025                 if (sdf_path[x-1]!='/' && x!=0)
4026                 {
4027                         sdf_path[x]='/';
4028                         sdf_path[x+1]=0;
4029                 }
4030         }
4031
4032         fprintf(stdout,"%s",header);
4033         fflush(stdout);
4034
4035         x=0;
4036         y=0;
4037
4038         min_lat=90;
4039         max_lat=-90;
4040
4041         min_lon=(int)floor(tx_site[0].lon);
4042         max_lon=(int)floor(tx_site[0].lon);
4043
4044         for (y=0, z=0; z<txsites; z++)
4045         {
4046                 txlat=(int)floor(tx_site[z].lat);
4047                 txlon=(int)floor(tx_site[z].lon);
4048
4049                 if (txlat<min_lat)
4050                         min_lat=txlat;
4051
4052                 if (txlat>max_lat)
4053                         max_lat=txlat;
4054
4055                 if (LonDiff(txlon,min_lon)<0.0)
4056                         min_lon=txlon;
4057
4058                 if (LonDiff(txlon,max_lon)>0.0)
4059                         max_lon=txlon;
4060         }
4061
4062         if (rxsite)
4063         {
4064                 rxlat=(int)floor(rx_site.lat);
4065                 rxlon=(int)floor(rx_site.lon);
4066
4067                 if (rxlat<min_lat)
4068                         min_lat=rxlat;
4069
4070                 if (rxlat>max_lat)
4071                         max_lat=rxlat;
4072
4073                 if (LonDiff(rxlon,min_lon)<0.0)
4074                         min_lon=rxlon;
4075
4076                 if (LonDiff(rxlon,max_lon)>0.0)
4077                         max_lon=rxlon;
4078         }
4079
4080
4081         /* Load the required SDF files */ 
4082
4083         width=ReduceAngle(max_lon-min_lon);
4084
4085         if ((max_lon-min_lon)<=180.0)
4086         {
4087                 for (y=0; y<=width; y++)
4088                         for (x=min_lat; x<=max_lat; x++)
4089                         {
4090                                 ymin=(int)(min_lon+(double)y);
4091
4092                                 while (ymin<0)
4093                                         ymin+=360;
4094
4095                                 while (ymin>=360)
4096                                         ymin-=360;
4097
4098                                 ymax=ymin+1;
4099
4100                                 while (ymax<0)
4101                                         ymax+=360;
4102
4103                                 while (ymax>=360)
4104                                         ymax-=360;
4105
4106                                 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4107                                 LoadSDF(string);
4108                         }
4109         }
4110
4111         else
4112         {
4113                 for (y=0; y<=width; y++)
4114                         for (x=min_lat; x<=max_lat; x++)
4115                         {
4116                                 ymin=max_lon+y;
4117
4118                                 while (ymin<0)
4119                                         ymin+=360;
4120
4121                                 while (ymin>=360)
4122                                         ymin-=360;
4123                                         
4124                                 ymax=ymin+1;
4125
4126                                 while (ymax<0)
4127                                         ymax+=360;
4128
4129                                 while (ymax>=360)
4130                                         ymax-=360;
4131
4132                                 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4133                                 LoadSDF(string);
4134                         }
4135         }
4136
4137         if (coverage | LRmap)
4138         {
4139                 if (LRmap)
4140                         txsites=1;
4141
4142                 for (z=0; z<txsites; z++)
4143                 {
4144                         /* "Ball park" estimates used to load any additional
4145                            SDF files required to conduct this analysis. */
4146
4147                         tx_range=sqrt(1.5*(tx_site[z].alt+GetElevation(tx_site[z])));
4148
4149                         if (LRmap)
4150                                 rx_range=sqrt(1.5*altitudeLR);
4151                         else
4152                                 rx_range=sqrt(1.5*altitude);
4153
4154                         /* deg_range determines the maximum
4155                            amount of topo data we read */
4156
4157                         deg_range=(tx_range+rx_range)/69.0;
4158
4159                         /* max_range sets the maximum size of the
4160                            analysis.  A small, non-zero amount can
4161                            be used to shrink the size of the analysis
4162                            and limit the amount of topo data read by
4163                            SPLAT!  A very large number will only increase
4164                            the width of the analysis, not the size of
4165                            the map. */
4166
4167                         if (max_range==0.0)
4168                                 max_range=tx_range+rx_range;
4169
4170                         if (max_range<(tx_range+rx_range))
4171                                 deg_range=max_range/69.0;
4172
4173                         /* Prevent the demand for a really wide coverage
4174                            from allocating more slots than are available
4175                            in memory. */
4176
4177                         switch (MAXSLOTS)
4178                         {
4179                                 case 2: deg_limit=0.25;
4180                                         break;
4181
4182                                 case 4: deg_limit=0.5;
4183                                         break;
4184
4185                                 case 9: deg_limit=1.0;
4186                                         break;
4187
4188                                 case 16: deg_limit=2.0;
4189                                         break;
4190
4191                                 case 25: deg_limit=3.0;
4192                         }
4193
4194                         if (tx_site[z].lat<70.0)
4195                                 deg_range_lon=deg_range/cos(deg2rad*tx_site[z].lat);
4196                         else
4197                                 deg_range_lon=deg_range/cos(deg2rad*70.0);
4198
4199                         /* Correct for squares in degrees not being square in miles */  
4200
4201                         if (deg_range>deg_limit)
4202                                 deg_range=deg_limit;
4203
4204                         if (deg_range_lon>deg_limit)
4205                                 deg_range_lon=deg_limit;
4206
4207
4208                         north_min=(int)floor(tx_site[z].lat-deg_range);
4209                         north_max=(int)floor(tx_site[z].lat+deg_range);
4210
4211                         west_min=(int)floor(tx_site[z].lon-deg_range_lon);
4212
4213                         while (west_min<0)
4214                                 west_min+=360;
4215
4216                         while (west_min>=360)
4217                                 west_min-=360;
4218
4219                         west_max=(int)floor(tx_site[z].lon+deg_range_lon);
4220
4221                         while (west_max<0)
4222                                 west_max+=360;
4223
4224                         while (west_max>=360)
4225                                 west_max-=360;
4226
4227                         if (north_min<min_lat)
4228                                 min_lat=north_min;
4229
4230                         if (north_max>max_lat)
4231                                 max_lat=north_max;
4232
4233                         if (LonDiff(west_min,min_lon)<0.0)
4234                                 min_lon=west_min;
4235
4236                         if (LonDiff(west_max,max_lon)>0.0)
4237                                 max_lon=west_max;
4238                 }
4239
4240
4241                 /* Load the required SDF files */ 
4242
4243                 width=ReduceAngle(max_lon-min_lon);
4244
4245                 if ((max_lon-min_lon)<=180.0)
4246                 {
4247                         for (y=0; y<=width; y++)
4248                                 for (x=min_lat; x<=max_lat; x++)
4249                                 {
4250                                         ymin=(int)(min_lon+(double)y);
4251
4252                                         while (ymin<0)
4253                                                 ymin+=360;
4254
4255                                         while (ymin>=360)
4256                                                 ymin-=360;
4257
4258                                         ymax=ymin+1;
4259
4260                                         while (ymax<0)
4261                                                 ymax+=360;
4262
4263                                         while (ymax>=360)
4264                                                 ymax-=360;
4265
4266                                         sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4267                                         LoadSDF(string);
4268                                 }
4269                 }
4270
4271                 else
4272                 {
4273                         for (y=0; y<=width; y++)
4274                                 for (x=min_lat; x<=max_lat; x++)
4275                                 {
4276                                         ymin=(int)(max_lon+(double)y);
4277
4278                                         while (ymin<0)
4279                                                 ymin+=360;
4280
4281                                         while (ymin>=360)
4282                                                 ymin-=360;
4283                                         
4284                                         ymax=ymin+1;
4285
4286                                         while (ymax<0)
4287                                                 ymax+=360;
4288
4289                                         while (ymax>=360)
4290                                                 ymax-=360;
4291
4292                                         sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4293                                         LoadSDF(string);
4294                                 }
4295                 }
4296         }
4297
4298
4299         if (mapfile[0])
4300                 map=1;
4301
4302         if (coverage | LRmap)
4303         {
4304                 for (x=0; x<txsites; x++)
4305                 {
4306                         if (coverage)
4307                                 PlotCoverage(tx_site[x],altitude);
4308
4309                         if (LRmap)
4310                                 PlotLRMap(tx_site[x],altitudeLR);
4311
4312                         PlaceMarker(tx_site[x]);
4313
4314                         if (report!='N')
4315                                 SiteReport(tx_site[x]);
4316                 }
4317
4318                 map=1;
4319         }
4320
4321         if (coverage==0 && LRmap==0)       
4322         {
4323                 PlaceMarker(rx_site);
4324
4325                 for (x=0; x<txsites; x++)
4326                 {
4327                         PlaceMarker(tx_site[x]);
4328
4329                         if (report=='y')
4330                         {
4331                                 switch (x)
4332                                 {
4333                                         case 0:
4334                                                 PlotPath(tx_site[x],rx_site,1);
4335                                                 break;
4336
4337                                         case 1:
4338                                                 PlotPath(tx_site[x],rx_site,8);
4339                                                 break;
4340
4341                                         case 2:
4342                                                 PlotPath(tx_site[x],rx_site,16);
4343                                                 break;
4344
4345                                         case 3:
4346                                                 PlotPath(tx_site[x],rx_site,32);
4347                                 }
4348                         }
4349
4350                         if (report!='N')
4351                                 ObstructionReport(tx_site[x],rx_site,report);
4352                 }
4353         }
4354
4355         if (map)
4356         {
4357                 if (bfs)
4358                 {
4359                         for (x=0; x<bfs; x++)
4360                                 LoadBoundaries(boundary_file[x]);
4361                 }
4362
4363                 if (cities)
4364                 {
4365                         for (x=0; x<cities; x++)
4366                                 LoadCities(city_file[x]);
4367                 }
4368                                 
4369                 if (!LRmap)
4370                         WritePPM(mapfile);
4371                 else
4372                         WritePPMLR(mapfile);
4373         }
4374
4375         if (terrain_plot)
4376         {
4377                 if (txsites>1)
4378                 {
4379                         for (x=0; terrain_file[x]!='.' && terrain_file[x]!=0 && x<80; x++);
4380
4381                         if (terrain_file[x]=='.')  /* extension */
4382                         {
4383                                 ext[0]='.';
4384                                 for (y=1, z=x, x++; terrain_file[x]!=0 && x<253 && y<14; x++, y++)
4385                                         ext[y]=terrain_file[x];
4386
4387                                 ext[y]=0;
4388                                 terrain_file[z]=0;
4389                         }
4390
4391                         else
4392                         {
4393                                 ext[0]=0;  /* No extension */
4394                                 terrain_file[x]=0;
4395                         }
4396
4397                         for (count=0; count<txsites; count++)
4398                         {
4399                                 sprintf(string,"%s-%c%s%c",terrain_file,'1'+count,ext,0);
4400                                 GraphTerrain(tx_site[count],rx_site,string);
4401                         }
4402                 }
4403
4404                 else
4405                         GraphTerrain(tx_site[0],rx_site,terrain_file);
4406         }
4407
4408         if (elevation_plot)
4409         {
4410                 if (txsites>1)
4411                 {
4412                         for (x=0; elevation_file[x]!='.' && elevation_file[x]!=0 && x<80; x++);
4413
4414                         if (elevation_file[x]=='.')  /* extension */
4415                         {
4416                                 ext[0]='.';
4417                                 for (y=1, z=x, x++; elevation_file[x]!=0 && x<253 && y<14; x++, y++)
4418                                         ext[y]=elevation_file[x];
4419
4420                                 ext[y]=0;
4421                                 elevation_file[z]=0;
4422                         }
4423
4424                         else
4425                         {
4426                                 ext[0]=0;  /* No extension */
4427                                 elevation_file[x]=0;
4428                         }
4429
4430                         for (count=0; count<txsites; count++)
4431                         {
4432                                 sprintf(string,"%s-%c%s%c",elevation_file,'1'+count,ext,0);
4433                                 GraphElevation(tx_site[count],rx_site,string);
4434                         }
4435                 }
4436
4437                 else
4438                         GraphElevation(tx_site[0],rx_site,elevation_file);
4439         }
4440
4441         if (height_plot)
4442         {
4443                 if (txsites>1)
4444                 {
4445                         for (x=0; height_file[x]!='.' && height_file[x]!=0 && x<80; x++);
4446
4447                         if (height_file[x]=='.')  /* extension */
4448                         {
4449                                 ext[0]='.';
4450                                 for (y=1, z=x, x++; height_file[x]!=0 && x<253 && y<14; x++, y++)
4451                                         ext[y]=height_file[x];
4452
4453                                 ext[y]=0;
4454                                 height_file[z]=0;
4455                         }
4456
4457                         else
4458                         {
4459                                 ext[0]=0;  /* No extension */
4460                                 height_file[x]=0;
4461                         }
4462
4463                         for (count=0; count<txsites; count++)
4464                         {
4465                                 sprintf(string,"%s-%c%s%c",height_file,'1'+count,ext,0);
4466                                 GraphHeight(tx_site[count],rx_site,string);
4467                         }
4468                 }
4469
4470                 else
4471                         GraphHeight(tx_site[0],rx_site,height_file);
4472         }
4473
4474         if (longley_plot)
4475         {
4476                 if (txsites>1)
4477                 {
4478                         for (x=0; longley_file[x]!='.' && longley_file[x]!=0 && x<80; x++);
4479
4480                         if (longley_file[x]=='.')  /* extension */
4481                         {
4482                                 ext[0]='.';
4483                                 for (y=1, z=x, x++; longley_file[x]!=0 && x<253 && y<14; x++, y++)
4484                                         ext[y]=longley_file[x];
4485
4486                                 ext[y]=0;
4487                                 longley_file[z]=0;
4488                         }
4489
4490                         else
4491                         {
4492                                 ext[0]=0;  /* No extension */
4493                                 longley_file[x]=0;
4494                         }
4495
4496                         for (count=0; count<txsites; count++)
4497                         {
4498                                 sprintf(string,"%s-%c%s%c",longley_file,'1'+count,ext,0);
4499                                 GraphLongley(tx_site[count],rx_site,string);
4500                         }
4501                 }
4502
4503                 else
4504                         GraphLongley(tx_site[0],rx_site,longley_file);
4505         }
4506
4507         return 0;
4508 }
4509