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 *****************************************************************************
8 * Extensively modified by J. D. McDonald in Jan. 2004 to include *
9 * the Longley-Rice propagation model using C++ code from NTIA/ITS. *
11 * See: http://flattop.its.bldrdoc.gov/itm.html *
13 *****************************************************************************
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 *
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 *
25 *****************************************************************************
26 g++ -Wall -O3 -s -lm -lbz2 -fomit-frame-pointer itm.cpp splat.cpp -o splat
27 *****************************************************************************/
37 #include "smallfont.h"
41 #define BZBUFFER 65536
44 #define ARRAYSIZE 4950
48 #define ARRAYSIZE 10870
52 #define ARRAYSIZE 19240
56 #define ARRAYSIZE 30025
59 char string[255], sdf_path[255], opened=0, *splat_version={"1.1.1"};
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;
66 int min_north=90, max_north=-90, min_west=360, max_west=-1,
67 max_elevation=-32768, min_elevation=32768, bzerror, maxdB=230;
69 struct site { double lat;
75 struct path { float lat[ARRAYSIZE];
77 float elevation[ARRAYSIZE];
78 float distance[ARRAYSIZE];
82 struct dem { int min_north;
88 short data[1200][1200];
89 unsigned char mask[1200][1200];
92 struct LR { double eps_dielect;
93 double sgm_conductivity;
94 double eno_ns_surfref;
102 double elev_l[ARRAYSIZE+10];
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);
109 double arccos(double x, double y)
111 /* This function implements the arc cosine function,
112 returning a value between 0 and TWOPI. */
125 int ReduceAngle(double angle)
127 /* This function normalizes the argument to
128 an integer angle between 0 and 180 degrees */
132 temp=acos(cos(angle*deg2rad));
134 return (int)rint(temp/deg2rad);
137 char *dec2dms(double decimal)
139 /* Converts decimal degrees to degrees, minutes, seconds,
140 (DMS) and returns the result as a character string. */
142 int degrees, minutes, seconds;
164 sprintf(string,"%d%c %d\' %d\"", degrees, 176, minutes, seconds);
168 int OrMask(double lat, double lon, int value)
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
176 int x, y, indx, minlat, minlon;
179 minlat=(int)floor(lat);
180 minlon=(int)floor(lon);
182 for (indx=0, found=0; indx<MAXSLOTS && found==0;)
183 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
190 x=(int)(1199.0*(lat-floor(lat)));
191 y=(int)(1199.0*(lon-floor(lon)));
193 dem[indx].mask[x][y]|=value;
195 return (dem[indx].mask[x][y]);
202 int GetMask(double lat, double lon)
204 /* This function returns the mask bits based on the latitude
205 and longitude given. */
207 return (OrMask(lat,lon,0));
210 double GetElevation(struct site location)
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. */
217 int x, y, indx, minlat, minlon;
222 minlat=(int)floor(location.lat);
223 minlon=(int)floor(location.lon);
225 x=(int)(1199.0*(location.lat-floor(location.lat)));
226 y=(int)(1199.0*(location.lon-floor(location.lon)));
228 for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
230 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
233 elevation=3.28084*dem[indx].data[x][y];
241 double Distance(struct site site1, struct site site2)
243 /* This function returns the great circle distance
244 in miles between any two site locations. */
246 double lat1, lon1, lat2, lon2, distance;
248 lat1=site1.lat*deg2rad;
249 lon1=site1.lon*deg2rad;
250 lat2=site2.lat*deg2rad;
251 lon2=site2.lon*deg2rad;
253 distance=3959.0*acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos((lon1)-(lon2)));
258 double Azimuth(struct site source, struct site destination)
260 /* This function returns the azimuth (in degrees) to the
261 destination as seen from the location of the source. */
263 double dest_lat, dest_lon, src_lat, src_lon,
264 beta, azimuth, diff, num, den, fraction;
266 dest_lat=destination.lat*deg2rad;
267 dest_lon=destination.lon*deg2rad;
269 src_lat=source.lat*deg2rad;
270 src_lon=source.lon*deg2rad;
272 /* Calculate Surface Distance */
274 beta=acos(sin(src_lat)*sin(dest_lat)+cos(src_lat)*cos(dest_lat)*cos(src_lon-dest_lon));
276 /* Calculate Azimuth */
278 num=sin(dest_lat)-(sin(src_lat)*cos(beta));
279 den=cos(src_lat)*sin(beta);
282 /* Trap potential problems in acos() due to rounding */
290 /* Calculate azimuth */
292 azimuth=acos(fraction);
294 /* Reference it to True North */
296 diff=dest_lon-src_lon;
305 azimuth=TWOPI-azimuth;
307 return (azimuth/deg2rad);
310 double ElevationAngle(struct site local, struct site remote)
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
319 register double a, b, dx;
321 a=GetElevation(remote)+remote.alt+earthradius;
322 b=GetElevation(local)+local.alt+earthradius;
324 dx=5280.0*Distance(local,remote);
326 /* Apply the Law of Cosines */
328 return ((180.0*(acos(((b*b)+(dx*dx)-(a*a))/(2.0*b*dx)))/PI)-90.0);
331 void ReadPath(struct site source, struct site destination)
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. */
340 double azimuth, distance, lat1, lon1, beta,
341 den, num, lat2, lon2, total_distance;
342 struct site tempsite;
344 lat1=source.lat*deg2rad;
345 lon1=source.lon*deg2rad;
346 azimuth=Azimuth(source,destination)*deg2rad;
347 total_distance=Distance(source,destination);
349 for (distance=0, c=0; distance<=total_distance; distance+=0.04)
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);
356 if (azimuth==0.0 && (beta>HALFPI-lat1))
359 else if (azimuth==HALFPI && (beta>HALFPI+lat1))
362 else if (fabs(num/den)>1.0)
367 if ((PI-azimuth)>=0.0)
368 lon2=lon1-arccos(num,den);
370 lon2=lon1+arccos(num,den);
388 path.elevation[c]=GetElevation(tempsite);
389 path.distance[c]=distance;
394 /* Make sure exact destination point is recorded at path.length-1 */
398 path.lat[c]=destination.lat;
399 path.lon[c]=destination.lon;
400 path.elevation[c]=GetElevation(destination);
401 path.distance[c]=total_distance;
408 path.length=ARRAYSIZE-1;
411 double AverageTerrain(struct site source, double azimuthx, double start_distance, double end_distance)
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. */
421 int c, samples, endpoint;
422 double beta, lat1, lon1, lat2, lon2, num, den, azimuth, terrain=0.0;
423 struct site destination;
425 lat1=source.lat*deg2rad;
426 lon1=source.lon*deg2rad;
428 /* Generate a path of elevations between the source
429 location and the remote location provided. */
431 beta=end_distance/3959.0;
433 azimuth=deg2rad*azimuthx;
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);
439 if (azimuth==0.0 && (beta>HALFPI-lat1))
442 else if (azimuth==HALFPI && (beta>HALFPI+lat1))
445 else if (fabs(num/den)>1.0)
450 if ((PI-azimuth)>=0.0)
451 lon2=lon1-arccos(num,den);
453 lon2=lon1+arccos(num,den);
465 destination.lat=lat2;
466 destination.lon=lon2;
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 */
472 if (GetElevation(destination)<-4999.0)
476 ReadPath(source,destination);
478 endpoint=path.length;
480 /* Shrink the length of the radial if the
481 outermost portion is not over U.S. land. */
483 for (c=endpoint-1; c>=0 && path.elevation[c]==0.0; c--);
487 for (c=0, samples=0; c<endpoint; c++)
489 if (path.distance[c]>=start_distance)
491 terrain+=path.elevation[c];
497 terrain=-5000.0; /* No land */
499 terrain=(terrain/(double)samples);
505 double haat(struct site antenna)
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. */
514 double terrain, avg_terrain, haat, sum=0.0;
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. */
520 for (c=0, azi=0; azi<=315 && error==0; azi+=45)
522 terrain=AverageTerrain(antenna, (double)azi, 2.0, 10.0);
524 if (terrain<-9998.0) /* SDF data is missing */
527 if (terrain>-4999.0) /* It's land, not water */
529 sum+=terrain; /* Sum of averages */
538 avg_terrain=(sum/(double)c);
539 haat=(antenna.alt+GetElevation(antenna))-avg_terrain;
544 float LonDiff(float lon1, float lon2)
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. */
565 void PlaceMarker(struct site location)
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. */
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;
590 if (lat<xmax && lat>xmin && (LonDiff(lon,ymax)<0.0) && (LonDiff(lon,ymin)>0.0))
602 /* Is Marker Position Clear Of Text Or Other Markers? */
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);
610 /* Determine Where Text Can Be Positioned */
612 /* label_length=length in pixels.
613 Each character is 8 pixels wide. */
615 label_length=p1*(double)(strlen(location.name)<<3);
617 if ((LonDiff(lon+label_length,ymax)<=0.0) && (LonDiff(lon-label_length,ymin)>=0.0))
619 /* Default: Centered Text */
621 texty=lon+label_length/2.0;
625 /* Position Text Below The Marker */
632 /* Is This Position Clear Of
633 Text Or Other Markers? */
635 for (a=0, occupied=0; a<16; a++)
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);
653 /* Position Text Above The Marker */
660 /* Is This Position Clear Of
661 Text Or Other Markers? */
663 for (a=0, occupied=0; a<16; a++)
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);
682 if (LonDiff(lon-label_length,ymin)>=0.0)
684 /* Position Text To The
685 Right Of The Marker */
693 /* Is This Position Clear Of
694 Text Or Other Markers? */
696 for (a=0, occupied=0; a<16; a++)
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);
714 /* Position Text To The
715 Left Of The Marker */
718 texty=lon+p8+(label_length);
723 /* Is This Position Clear Of
724 Text Or Other Markers? */
726 for (a=0, occupied=0; a<16; a++)
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);
743 /* textx and texty contain the latitude and longitude
744 coordinates that describe the placement of the text
754 for (a=0; a<16 && ok2print; a++)
756 for (b=0; b<(int)strlen(location.name); b++)
758 byte=fontdata[16*(location.name[b])+a];
760 for (c=128; c>0; c=c>>1, y-=p1)
769 /* Draw Square Marker Centered
770 On Location Specified */
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)
780 double ReadBearing(char *input)
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. */
791 double seconds, bearing=0.0;
793 int a, b, length, degrees, minutes;
795 /* Copy "input" to "string", and ignore any extra
796 spaces that might be present in the process. */
799 length=strlen(input);
801 for (a=0, b=0; a<length && a<18; a++)
803 if ((input[a]!=32 && input[a]!='\n') || (input[a]==32 && input[a+1]!=32 && b!=0))
812 /* Count number of spaces in the clean string. */
814 length=strlen(string);
816 for (a=0, b=0; a<length; a++)
820 if (b==0) /* Decimal Format (40.139722) */
821 sscanf(string,"%lf",&bearing);
823 if (b==2) /* Degree, Minute, Second Format (40 08 23) */
825 sscanf(string,"%d %d %lf",°rees, &minutes, &seconds);
826 bearing=(double)degrees+((double)minutes/60)+(seconds/3600);
829 /* Anything else returns a 0.0 */
831 if (bearing>360.0 || bearing<-90.0)
837 struct site LoadQTH(char *filename)
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. */
848 char string[50], qthfile[255];
849 struct site tempsite;
852 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
853 qthfile[x]=filename[x];
866 fd=fopen(qthfile,"r");
873 /* Strip <CR> and/or <LF> from end of site name */
875 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; tempsite.name[x]=string[x], x++);
881 tempsite.lat=ReadBearing(string);
885 tempsite.lon=ReadBearing(string);
891 /* Remove <CR> and/or <LF> from antenna height string */
892 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; x++);
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. */
902 for (x=0; string[x]!='M' && string[x]!='m' && string[x]!=0 && x<48; x++);
903 if (string[x]=='M' || string[x]=='m')
906 sscanf(string,"%lf",&tempsite.alt);
907 tempsite.alt*=3.28084;
913 sscanf(string,"%lf",&tempsite.alt);
920 int LoadSDF_SDF(char *name)
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
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];
932 for (x=0; name[x]!='.' && name[x]!=0 && x<250; x++)
937 /* Parse filename for minimum latitude and longitude values */
939 sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
947 /* Is it already in memory? */
949 for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
951 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
955 /* Is room available to load it? */
959 for (indx=0, free_slot=0; indx<MAXSLOTS && free_slot==0; indx++)
960 if (dem[indx].max_north==-90)
966 if (free_slot && found==0 && indx>=0 && indx<MAXSLOTS)
968 /* Search for SDF file in current working directory first */
970 strncpy(path_plus_name,sdf_file,255);
972 fd=fopen(path_plus_name,"rb");
976 /* Next, try loading SDF file from path specified
977 in $HOME/.splat_path file or by -d argument */
979 strncpy(path_plus_name,sdf_path,255);
980 strncat(path_plus_name,sdf_file,255);
982 fd=fopen(path_plus_name,"rb");
987 fprintf(stdout,"Loading \"%s\" into slot %d...",path_plus_name,indx+1);
991 sscanf(line,"%d",&dem[indx].max_west);
994 sscanf(line,"%d",&dem[indx].min_north);
997 sscanf(line,"%d",&dem[indx].min_west);
1000 sscanf(line,"%d",&dem[indx].max_north);
1002 for (x=0; x<1200; x++)
1003 for (y=0; y<1200; y++)
1006 sscanf(line,"%d",&data);
1008 dem[indx].data[x][y]=data;
1010 if (data>dem[indx].max_el)
1011 dem[indx].max_el=data;
1013 if (data<dem[indx].min_el)
1014 dem[indx].min_el=data;
1019 if (dem[indx].min_el<min_elevation)
1020 min_elevation=dem[indx].min_el;
1022 if (dem[indx].max_el>max_elevation)
1023 max_elevation=dem[indx].max_el;
1026 max_north=dem[indx].max_north;
1028 else if (dem[indx].max_north>max_north)
1029 max_north=dem[indx].max_north;
1032 min_north=dem[indx].min_north;
1034 else if (dem[indx].min_north<min_north)
1035 min_north=dem[indx].min_north;
1038 max_west=dem[indx].max_west;
1042 if (abs(dem[indx].max_west-max_west)<180)
1044 if (dem[indx].max_west>max_west)
1045 max_west=dem[indx].max_west;
1050 if (dem[indx].max_west<max_west)
1051 max_west=dem[indx].max_west;
1056 min_west=dem[indx].min_west;
1060 if (abs(dem[indx].min_west-min_west)<180)
1062 if (dem[indx].min_west<min_west)
1063 min_west=dem[indx].min_west;
1068 if (dem[indx].min_west>min_west)
1069 min_west=dem[indx].min_west;
1073 fprintf(stdout," Done!\n");
1086 char *BZfgets(BZFILE *bzfd, unsigned length)
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. */
1096 static int x, y, nBuf;
1097 static char buffer[BZBUFFER+1], output[BZBUFFER+1];
1100 if (opened!=1 && bzerror==BZ_OK)
1102 /* First time through. Initialize everything! */
1113 if (x==nBuf && bzerror!=BZ_STREAM_END && bzerror==BZ_OK && opened)
1115 /* Uncompress data into a static buffer */
1117 nBuf=BZ2_bzRead(&bzerror, bzfd, buffer, BZBUFFER);
1122 /* Build a string from buffer contents */
1124 output[y]=buffer[x];
1126 if (output[y]=='\n' || output[y]==0 || y==(int)length-1)
1145 int LoadSDF_BZ(char *name)
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. */
1152 int x, y, data, indx, minlat, minlon, maxlat, maxlon;
1153 char found, free_slot=0, sdf_file[255], path_plus_name[255];
1157 for (x=0; name[x]!='.' && name[x]!=0 && x<247; x++)
1158 sdf_file[x]=name[x];
1162 /* Parse sdf_file name for minimum latitude and longitude values */
1164 sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1176 /* Is it already in memory? */
1178 for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
1180 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1184 /* Is room available to load it? */
1188 for (indx=0, free_slot=0; indx<MAXSLOTS && free_slot==0; indx++)
1189 if (dem[indx].max_north==-90)
1195 if (free_slot && found==0 && indx>=0 && indx<MAXSLOTS)
1197 /* Search for SDF file in current working directory first */
1199 strncpy(path_plus_name,sdf_file,255);
1201 fd=fopen(path_plus_name,"rb");
1202 bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1204 if (fd==NULL || bzerror!=BZ_OK)
1206 /* Next, try loading SDF file from path specified
1207 in $HOME/.splat_path file or by -d argument */
1209 strncpy(path_plus_name,sdf_path,255);
1210 strncat(path_plus_name,sdf_file,255);
1212 fd=fopen(path_plus_name,"rb");
1213 bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1216 if (fd!=NULL && bzerror==BZ_OK)
1218 fprintf(stdout,"Loading \"%s\" into slot %d...",path_plus_name,indx+1);
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);
1226 for (x=0; x<1200; x++)
1227 for (y=0; y<1200; y++)
1229 sscanf(BZfgets(bzfd,20),"%d",&data);
1231 dem[indx].data[x][y]=data;
1233 if (data>dem[indx].max_el)
1234 dem[indx].max_el=data;
1236 if (data<dem[indx].min_el)
1237 dem[indx].min_el=data;
1242 BZ2_bzReadClose(&bzerror,bzfd);
1244 if (dem[indx].min_el<min_elevation)
1245 min_elevation=dem[indx].min_el;
1247 if (dem[indx].max_el>max_elevation)
1248 max_elevation=dem[indx].max_el;
1251 max_north=dem[indx].max_north;
1253 else if (dem[indx].max_north>max_north)
1254 max_north=dem[indx].max_north;
1257 min_north=dem[indx].min_north;
1259 else if (dem[indx].min_north<min_north)
1260 min_north=dem[indx].min_north;
1263 max_west=dem[indx].max_west;
1267 if (abs(dem[indx].max_west-max_west)<180)
1269 if (dem[indx].max_west>max_west)
1270 max_west=dem[indx].max_west;
1275 if (dem[indx].max_west<max_west)
1276 max_west=dem[indx].max_west;
1281 min_west=dem[indx].min_west;
1285 if (abs(dem[indx].min_west-min_west)<180)
1287 if (dem[indx].min_west<min_west)
1288 min_west=dem[indx].min_west;
1293 if (dem[indx].min_west>min_west)
1294 min_west=dem[indx].min_west;
1298 fprintf(stdout," Done!\n");
1309 char LoadSDF(char *name)
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. */
1320 int x, y, indx, minlat, minlon, maxlat, maxlon;
1321 char found, free_slot=0;
1322 int return_value=-1;
1324 /* Try to load an uncompressed SDF first. */
1326 return_value=LoadSDF_SDF(name);
1328 /* If that fails, try loading a compressed SDF. */
1330 if (return_value==0 || return_value==-1)
1331 return_value=LoadSDF_BZ(name);
1333 /* If neither format can be found, then assume the area is water. */
1335 if (return_value==0 || return_value==-1)
1337 /* Parse SDF name for minimum latitude and longitude values */
1339 sscanf(name,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1341 /* Is it already in memory? */
1343 for (indx=0, found=0; indx<MAXSLOTS && found==0; indx++)
1345 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1349 /* Is room available to load it? */
1353 for (indx=0, free_slot=0; indx<MAXSLOTS && free_slot==0; indx++)
1354 if (dem[indx].max_north==-90)
1360 if (free_slot && found==0 && indx>=0 && indx<MAXSLOTS)
1362 fprintf(stdout,"Region \"%s\" assumed as sea-level into slot %d...",name,indx+1);
1365 dem[indx].max_west=maxlon;
1366 dem[indx].min_north=minlat;
1367 dem[indx].min_west=minlon;
1368 dem[indx].max_north=maxlat;
1370 /* Fill DEM with sea-level topography */
1372 for (x=0; x<1200; x++)
1373 for (y=0; y<1200; y++)
1375 dem[indx].data[x][y]=0;
1377 if (dem[indx].min_el>0)
1381 if (dem[indx].min_el<min_elevation)
1382 min_elevation=dem[indx].min_el;
1384 if (dem[indx].max_el>max_elevation)
1385 max_elevation=dem[indx].max_el;
1388 max_north=dem[indx].max_north;
1390 else if (dem[indx].max_north>max_north)
1391 max_north=dem[indx].max_north;
1394 min_north=dem[indx].min_north;
1396 else if (dem[indx].min_north<min_north)
1397 min_north=dem[indx].min_north;
1400 max_west=dem[indx].max_west;
1404 if (abs(dem[indx].max_west-max_west)<180)
1406 if (dem[indx].max_west>max_west)
1407 max_west=dem[indx].max_west;
1412 if (dem[indx].max_west<max_west)
1413 max_west=dem[indx].max_west;
1418 min_west=dem[indx].min_west;
1422 if (abs(dem[indx].min_west-min_west)<180)
1424 if (dem[indx].min_west<min_west)
1425 min_west=dem[indx].min_west;
1430 if (dem[indx].min_west>min_west)
1431 min_west=dem[indx].min_west;
1435 fprintf(stdout," Done!\n");
1442 return return_value;
1445 void LoadCities(char *filename)
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! */
1452 char input[80], str[3][80];
1453 struct site city_site;
1456 fd=fopen(filename,"r");
1462 fprintf(stdout,"Reading \"%s\"... ",filename);
1465 while (fd!=NULL && feof(fd)==0)
1467 /* Parse line for name, latitude, and longitude */
1469 for (x=0, y=0, z=0; x<78 && input[x]!=0 && z<3; x++)
1471 if (input[x]!=',' && y<78)
1485 strncpy(city_site.name,str[0],49);
1486 city_site.lat=ReadBearing(str[1]);
1487 city_site.lon=ReadBearing(str[2]);
1490 PlaceMarker(city_site);
1496 fprintf(stdout,"Done!\n");
1500 fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename);
1503 void LoadBoundaries(char *filename)
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. */
1512 double lat0, lon0, lat1, lon1;
1514 struct site source, destination;
1517 fd=fopen(filename,"r");
1521 fgets(string,78,fd);
1523 fprintf(stdout,"Reading \"%s\"... ",filename);
1528 fgets(string,78,fd);
1529 sscanf(string,"%lf %lf", &lon0, &lat0);
1530 fgets(string,78,fd);
1534 sscanf(string,"%lf %lf", &lon1, &lat1);
1541 destination.lat=lat1;
1542 destination.lon=lon1;
1544 ReadPath(source,destination);
1546 for (x=0; x<path.length; x++)
1547 OrMask(path.lat[x],path.lon[x],4);
1552 fgets(string,78,fd);
1554 } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
1556 fgets(string,78,fd);
1558 } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
1562 fprintf(stdout,"Done!\n");
1567 fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename);
1570 void ReadLRParm(char *txsite_filename)
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". */
1581 char filename[255], lookup[256], string[80];
1583 FILE *fd=NULL, *outfile=NULL;
1585 /* Default parameters in case things go bad */
1587 LR.eps_dielect=15.0;
1588 LR.sgm_conductivity=0.005;
1589 LR.eno_ns_surfref=301.0;
1596 /* Modify txsite filename to one with a .lrp extension. */
1598 strncpy(filename,txsite_filename,255);
1600 for (x=0; filename[x]!='.' && filename[x]!=0 && filename[x]!='\n' && x<249; x++);
1608 /* Small lookup table to parse file, pass
1609 numeric data, and ignore comments. */
1611 for (x=0; x<=255; x++)
1614 /* Valid characters */
1616 for (x=48; x<=57; x++)
1622 fd=fopen(filename,"r");
1626 /* Load default "splat.lrp" file */
1628 strncpy(filename,"splat.lrp\0",10);
1629 fd=fopen(filename,"r");
1634 fgets(string,80,fd);
1636 for (x=0; lookup[(int)string[x]] && x<20; x++)
1637 string[x]=lookup[(int)string[x]];
1641 ok=sscanf(string,"%lf", &din);
1647 fgets(string,80,fd);
1649 for (x=0; lookup[(int)string[x]] && x<20; x++)
1650 string[x]=lookup[(int)string[x]];
1654 ok=sscanf(string,"%lf", &din);
1659 LR.sgm_conductivity=din;
1661 fgets(string,80,fd);
1663 for (x=0; lookup[(int)string[x]] && x<20; x++)
1664 string[x]=lookup[(int)string[x]];
1668 ok=sscanf(string,"%lf", &din);
1673 LR.eno_ns_surfref=din;
1675 fgets(string,80,fd);
1677 for (x=0; lookup[(int)string[x]] && x<20; x++)
1678 string[x]=lookup[(int)string[x]];
1682 ok=sscanf(string,"%lf", &din);
1689 fgets(string,80,fd);
1691 for (x=0; lookup[(int)string[x]] && x<20; x++)
1692 string[x]=lookup[(int)string[x]];
1696 ok=sscanf(string,"%d", &iin);
1701 LR.radio_climate=iin;
1703 fgets(string,80,fd);
1705 for (x=0; lookup[(int)string[x]] && x<20; x++)
1706 string[x]=lookup[(int)string[x]];
1710 ok=sscanf(string,"%d", &iin);
1717 fgets(string,80,fd);
1719 for (x=0; lookup[(int)string[x]] && x<20; x++)
1720 string[x]=lookup[(int)string[x]];
1724 ok=sscanf(string,"%lf", &din);
1731 fgets(string,80,fd);
1733 for (x=0; lookup[(int)string[x]] && x<20; x++)
1734 string[x]=lookup[(int)string[x]];
1738 ok=sscanf(string,"%lf", &din);
1749 /* Create a "splat.lrp" file since one
1750 could not be successfully loaded. */
1752 outfile=fopen("splat.lrp","w");
1754 fprintf(outfile,"%.3f\t; Earth Dielectric Constant (Relative permittivity)\n",LR.eps_dielect);
1756 fprintf(outfile,"%.3f\t; Earth Conductivity (Siemens per meter)\n", LR.sgm_conductivity);
1758 fprintf(outfile,"%.3f\t; Atmospheric Bending Constant (N-Units)\n",LR.eno_ns_surfref);
1760 fprintf(outfile,"%.3f\t; Frequency in MHz (20 MHz to 20 GHz)\n", LR.frq_mhz);
1762 fprintf(outfile,"%d\t; Radio Climate\n",LR.radio_climate);
1764 fprintf(outfile,"%d\t; Polarization (0 = Horizontal, 1 = Vertical)\n", LR.pol);
1766 fprintf(outfile,"%.2f\t; Fraction of situations\n",LR.conf);
1768 fprintf(outfile, "%.2f\t; Fraction of time\n",LR.rel);
1770 fprintf(outfile,"\nPlease consult SPLAT! documentation for the meaning and use of this data.\n");
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);
1777 if (fd==NULL || ok==0)
1778 fprintf(stderr,"Longley-Rice default parameters have been assumed for this analysis.\n");
1781 struct site los(struct site source, struct site destination)
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. */
1797 struct site test, blockage;
1798 register double distance, tx_alt, rx_alt,
1799 cos_xmtr_angle, cos_test_angle, test_alt;
1801 ReadPath(source,destination);
1803 distance=5280.0*Distance(source,destination);
1804 tx_alt=earthradius+source.alt+GetElevation(source);
1805 rx_alt=earthradius+destination.alt+GetElevation(destination);
1807 /* Elevation angle of the xmtr (source) as seen by the rcvr */
1809 cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
1811 /* Determine the elevation angle of each discrete location
1812 along the path between the receiver and transmitter.
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. */
1823 for (x=path.length-1, block=0; x>0 && block==0; x--)
1825 /* Build a structure for each test
1826 point along the path to be surveyed. */
1828 test.lat=path.lat[x];
1829 test.lon=path.lon[x];
1831 /* Measure the distance between the
1832 test point and the receiver locations */
1834 distance=5280.0*Distance(test,destination);
1835 test_alt=earthradius+path.elevation[x];
1837 /* Determine the cosine of the elevation of the test
1838 point as seen from the location of the receiver */
1840 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
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
1852 if (cos_xmtr_angle>cos_test_angle)
1855 blockage.lat=path.lat[x];
1856 blockage.lon=path.lon[x];
1857 blockage.alt=path.elevation[x];
1858 blockage.name[0]='*';
1867 blockage.name[0]=' ';
1873 void PlotPath(struct site source, struct site destination, char mask_value)
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!. */
1886 register double cos_xmtr_angle, cos_test_angle, test_alt;
1887 double distance, rx_alt, tx_alt;
1889 ReadPath(source,destination);
1891 for (y=0; y<path.length; y++)
1893 /* Test this point only if it hasn't been already
1894 tested and found to be free of obstructions. */
1896 if ((GetMask(path.lat[y],path.lon[y])&mask_value)==0)
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];
1902 /* Calculate the cosine of the elevation of the
1903 transmitter as seen at the temp rx point. */
1905 cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
1907 for (x=y, block=0; x>=0 && block==0; x--)
1909 distance=5280.0*(path.distance[y]-path.distance[x]);
1910 test_alt=earthradius+path.elevation[x];
1912 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
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. */
1921 if (cos_xmtr_angle>cos_test_angle)
1926 OrMask(path.lat[y],path.lon[y],mask_value);
1931 void PlotLRPath(struct site source, struct site destination)
1933 /* This function plots the RF signal path loss
1934 between source and destination points based
1935 on the Longley-Rice propagation model. */
1941 ReadPath(source,destination);
1942 elev_l[1]=0.04*METERS_PER_MILE;
1944 for (x=0; x<path.length; x++)
1945 elev_l[x+2]=path.elevation[x]*METERS_PER_FOOT;
1947 for (y=0; y<path.length; y++)
1949 /* Test this point only if it has not already been tested. */
1951 if (GetMask(path.lat[y],path.lon[y])==0 && 0.04*y<=max_range)
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);
1961 /* Note: PASS BY REFERENCE ... loss and errnum are
1962 passed by reference, and are only used in this
1963 file by this function */
1975 OrMask(path.lat[y],path.lon[y],((unsigned char)(loss))<<3);
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);
1983 void PlotCoverage(struct site source, double altitude)
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. */
1994 float lat, lon, one_pixel;
1995 static unsigned char mask_value;
1998 unsigned char symbol[4], x;
2000 /* Initialize mask_value */
2002 if (mask_value!=8 && mask_value!=16 && mask_value!=32)
2005 one_pixel=1.0/1200.0;
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);
2017 /* 18.75=1200 pixels/degree divided by 64 loops
2018 per progress indicator symbol (.oOo) printed. */
2020 z=(int)(18.75*ReduceAngle(max_west-min_west));
2022 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2031 PlotPath(source,edge,mask_value);
2036 fprintf(stdout,"%c",symbol[x]);
2048 fprintf(stdout,"\n25%c to 50%c ",37,37);
2051 z=(int)(18.75*(max_north-min_north));
2053 for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel)
2059 PlotPath(source,edge,mask_value);
2064 fprintf(stdout,"%c",symbol[x]);
2076 fprintf(stdout,"\n50%c to 75%c ",37,37);
2079 z=(int)(18.75*ReduceAngle(max_west-min_west));
2081 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2090 PlotPath(source,edge,mask_value);
2095 fprintf(stdout,"%c",symbol[x]);
2107 fprintf(stdout,"\n75%c to 100%c ",37,37);
2110 z=(int)(18.75*(max_north-min_north));
2112 for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel)
2118 PlotPath(source,edge,mask_value);
2123 fprintf(stdout,"%c",symbol[x]);
2134 fprintf(stdout,"\nDone!\n");
2137 /* Assign next mask value */
2154 void PlotLRMap(struct site source, double altitude)
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. */
2167 float lat, lon, one_pixel;
2168 unsigned char symbol[4], x;
2170 one_pixel=1.0/1200.0;
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);
2183 /* 18.75=1200 pixels/degree divided by 64 loops
2184 per progress indicator symbol (.oOo) printed. */
2186 z=(int)(18.75*ReduceAngle(max_west-min_west));
2188 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2197 PlotLRPath(source,edge);
2202 fprintf(stdout,"%c",symbol[x]);
2214 fprintf(stdout,"\n25%c to 50%c ",37,37);
2217 z=(int)(18.75*(max_north-min_north));
2219 for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel)
2225 PlotLRPath(source,edge);
2230 fprintf(stdout,"%c",symbol[x]);
2242 fprintf(stdout,"\n50%c to 75%c ",37,37);
2245 z=(int)(18.75*ReduceAngle(max_west-min_west));
2247 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2256 PlotLRPath(source,edge);
2261 fprintf(stdout,"%c",symbol[x]);
2273 fprintf(stdout,"\n75%c to 100%c ",37,37);
2276 z=(int)(18.75*(max_north-min_north));
2278 for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel)
2284 PlotLRPath(source,edge);
2289 fprintf(stdout,"%c",symbol[x]);
2300 fprintf(stdout,"\nDone!\n");
2304 void WritePPM(char *filename)
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. */
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;
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);
2324 width=(unsigned)(1200*ReduceAngle(max_west-min_west));
2325 height=(unsigned)(1200*ReduceAngle(max_north-min_north));
2328 strncpy(mapfile, "map.ppm\0",8);
2331 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
2332 mapfile[x]=filename[x];
2341 fd=fopen(mapfile,"wb");
2343 fprintf(fd,"P6\n%u %u\n255\n",width,height);
2345 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height);
2348 for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel)
2350 minlat=(int)floor(lat);
2352 for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel)
2357 minlon=(int)floor(lon);
2359 for (indx=0, found=0; indx<MAXSLOTS && found==0;)
2360 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
2367 x0=(int)(1199.0*(lat-floor(lat)));
2368 y0=(int)(1199.0*(lon-floor(lon)));
2370 mask=dem[indx].mask[x0][y0];
2373 /* Text Labels: Red */
2374 fprintf(fd,"%c%c%c",255,0,0);
2377 /* County Boundaries: Light Cyan */
2378 fprintf(fd,"%c%c%c",128,128,255);
2380 else switch (mask&57)
2384 fprintf(fd,"%c%c%c",0,255,0);
2389 fprintf(fd,"%c%c%c",0,255,255);
2393 /* TX1 + TX2: Yellow */
2394 fprintf(fd,"%c%c%c",255,255,0);
2398 /* TX3: Medium Violet */
2399 fprintf(fd,"%c%c%c",147,112,219);
2403 /* TX1 + TX3: Pink */
2404 fprintf(fd,"%c%c%c",255,192,203);
2408 /* TX2 + TX3: Orange */
2409 fprintf(fd,"%c%c%c",255,165,0);
2413 /* TX1 + TX2 + TX3: Dark Green */
2414 fprintf(fd,"%c%c%c",0,100,0);
2419 fprintf(fd,"%c%c%c",255,130,71);
2423 /* TX1 + TX4: Green Yellow */
2424 fprintf(fd,"%c%c%c",173,255,47);
2428 /* TX2 + TX4: Dark Sea Green 1 */
2429 fprintf(fd,"%c%c%c",193,255,193);
2433 /* TX1 + TX2 + TX4: Blanched Almond */
2434 fprintf(fd,"%c%c%c",255,235,205);
2438 /* TX3 + TX4: Dark Turquoise */
2439 fprintf(fd,"%c%c%c",0,206,209);
2443 /* TX1 + TX3 + TX4: Medium Spring Green */
2444 fprintf(fd,"%c%c%c",0,250,154);
2448 /* TX2 + TX3 + TX4: Tan */
2449 fprintf(fd,"%c%c%c",210,180,140);
2453 /* TX1 + TX2 + TX3 + TX4: Gold2 */
2454 fprintf(fd,"%c%c%c",238,201,0);
2458 /* Water: Medium Blue */
2459 if (dem[indx].data[x0][y0]==0)
2460 fprintf(fd,"%c%c%c",0,0,170);
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);
2472 /* We should never get here, but if */
2473 /* we do, display the region as black */
2475 fprintf(fd,"%c%c%c",0,0,0);
2481 fprintf(stdout,"Done!\n");
2485 void WritePPMLR(char *filename)
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. */
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;
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);
2504 width=(unsigned)(1200*ReduceAngle(max_west-min_west));
2505 height=(unsigned)(1200*ReduceAngle(max_north-min_north));
2508 strncpy(mapfile, "map.ppm\0",8);
2511 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
2512 mapfile[x]=filename[x];
2521 fd=fopen(mapfile,"wb");
2523 fprintf(fd,"P6\n%u %u\n255\n",width,height+30);
2525 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height+30);
2528 for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel)
2530 minlat=(int)floor(lat);
2532 for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel)
2537 minlon=(int)floor(lon);
2539 for (indx=0, found=0; indx<MAXSLOTS && found==0;)
2540 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west)
2546 x0=(int)(1199.0*(lat-floor(lat)));
2547 y0=(int)(1199.0*(lon-floor(lon)));
2549 mask=dem[indx].mask[x0][y0];
2550 loss=70+(10*(int)((mask&248)>>3));
2555 /* Text Labels - Black or Red */
2557 /* if ((mask&120) && (loss<=maxdB)) */
2558 if ((mask&120) && (loss<=90))
2559 fprintf(fd,"%c%c%c",0,0,0);
2561 fprintf(fd,"%c%c%c",255,0,0);
2568 /* County Boundaries: Black */
2570 fprintf(fd,"%c%c%c",0,0,0);
2575 if (cityorcounty==0)
2579 { /* Display land or sea elevation */
2581 if (dem[indx].data[x0][y0]==0)
2582 fprintf(fd,"%c%c%c",0,0,170);
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);
2593 /* Plot signal loss in color */
2596 fprintf(fd,"%c%c%c",255,0,0);
2600 fprintf(fd,"%c%c%c",255,128,0);
2604 fprintf(fd,"%c%c%c",255,165,0);
2608 fprintf(fd,"%c%c%c",255,206,0);
2612 fprintf(fd,"%c%c%c",255,255,0);
2616 fprintf(fd,"%c%c%c",184,255,0);
2620 fprintf(fd,"%c%c%c",0,255,0);
2624 fprintf(fd,"%c%c%c",0,208,0);
2628 fprintf(fd,"%c%c%c",0,196,196);
2632 fprintf(fd,"%c%c%c",0,148,255);
2636 fprintf(fd,"%c%c%c",80,80,255);
2640 fprintf(fd,"%c%c%c",0,38,255);
2644 fprintf(fd,"%c%c%c",142,63,255);
2648 fprintf(fd,"%c%c%c",196,54,255);
2652 fprintf(fd,"%c%c%c",255,0,255);
2656 fprintf(fd,"%c%c%c",255,194,204);
2661 if (dem[indx].data[x0][y0]==0)
2662 fprintf(fd,"%c%c%c",0,0,170);
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);
2675 /* We should never get here, but if */
2676 /* we do, display the region as black */
2678 fprintf(fd,"%c%c%c",0,0,0);
2683 /* Display legend along bottom of image */
2687 for (y0=0; y0<30; y0++)
2689 for (indx=0; indx<16; indx++)
2691 for (x=0; x<x0; x++)
2696 if (y0>=10 && y0<=18)
2701 if (smallfont[t2/10][y0-10][x-11])
2706 if (smallfont[t2%10][y0-10][x-19])
2710 if (smallfont[0][y0-10][x-27])
2717 fprintf(fd,"%c%c%c",255,0,0);
2721 fprintf(fd,"%c%c%c",255,128,0);
2725 fprintf(fd,"%c%c%c",255,165,0);
2729 fprintf(fd,"%c%c%c",255,206,0);
2733 fprintf(fd,"%c%c%c",255,255,0);
2737 fprintf(fd,"%c%c%c",184,255,0);
2741 fprintf(fd,"%c%c%c",0,255,0);
2745 fprintf(fd,"%c%c%c",0,208,0);
2749 fprintf(fd,"%c%c%c",0,196,196);
2753 fprintf(fd,"%c%c%c",0,148,255);
2757 fprintf(fd,"%c%c%c",80,80,255);
2761 fprintf(fd,"%c%c%c",0,38,255);
2765 fprintf(fd,"%c%c%c",142,63,255);
2769 fprintf(fd,"%c%c%c",196,54,255);
2773 fprintf(fd,"%c%c%c",255,0,255);
2778 fprintf(fd,"%c%c%c",0,0,0);
2782 fprintf(fd,"%c%c%c",255,194,204);
2789 fprintf(stdout,"Done!\n");
2793 void GraphTerrain(struct site source, struct site destination, char *name)
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. */
2803 char filename[255], term[15], ext[15];
2806 ReadPath(destination,source);
2808 fd=fopen("profile.gp","wb");
2810 for (x=0; x<path.length; x++)
2811 fprintf(fd,"%f\t%f\n",path.distance[x],path.elevation[x]);
2817 /* Default filename and output file type */
2819 strncpy(filename,"profile\0",8);
2820 strncpy(term,"gif\0",4);
2821 strncpy(ext,"gif\0",4);
2826 /* Grab extension and terminal type from "name" */
2828 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
2829 filename[x]=name[x];
2833 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
2835 term[y]=tolower(name[x]);
2845 { /* No extension -- Default is gif */
2848 strncpy(term,"gif\0",4);
2849 strncpy(ext,"gif\0",4);
2853 /* Either .ps or .postscript may be used
2854 as an extension for postscript output. */
2856 if (strncmp(term,"postscript",10)==0)
2857 strncpy(ext,"ps\0",3);
2859 else if (strncmp(ext,"ps",2)==0)
2860 strncpy(term,"postscript\0",11);
2862 fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
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");
2876 x=system("gnuplot splat.gp");
2881 unlink("profile.gp");
2882 fprintf(stdout," Done!\n");
2887 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
2890 void GraphElevation(struct site source, struct site destination, char *name)
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. */
2900 char filename[255], term[15], ext[15];
2901 double angle, refangle, maxangle=-90.0;
2903 FILE *fd=NULL, *fd2=NULL;
2905 ReadPath(destination,source); /* destination=RX, source=TX */
2906 refangle=ElevationAngle(destination,source);
2908 fd=fopen("profile.gp","wb");
2909 fd2=fopen("reference.gp","wb");
2911 for (x=1; x<path.length-1; x++)
2913 remote.lat=path.lat[x];
2914 remote.lon=path.lon[x];
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);
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);
2932 /* Default filename and output file type */
2934 strncpy(filename,"profile\0",8);
2935 strncpy(term,"gif\0",4);
2936 strncpy(ext,"gif\0",4);
2941 /* Grab extension and terminal type from "name" */
2943 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
2944 filename[x]=name[x];
2948 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
2950 term[y]=tolower(name[x]);
2960 { /* No extension -- Default is gif */
2963 strncpy(term,"gif\0",4);
2964 strncpy(ext,"gif\0",4);
2968 /* Either .ps or .postscript may be used
2969 as an extension for postscript output. */
2971 if (strncmp(term,"postscript",10)==0)
2972 strncpy(ext,"ps\0",3);
2974 else if (strncmp(ext,"ps",2)==0)
2975 strncpy(term,"postscript\0",11);
2977 fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
2980 fd=fopen("splat.gp","w");
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");
2993 x=system("gnuplot splat.gp");
2998 unlink("profile.gp");
2999 unlink("reference.gp");
3001 fprintf(stdout," Done!\n");
3006 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
3009 void GraphHeight(struct site source, struct site destination, char *name)
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. */
3021 char filename[255], term[15], ext[15];
3022 double a, b, c, height, refangle, cangle, maxheight=-100000.0,
3025 FILE *fd=NULL, *fd2=NULL;
3027 ReadPath(destination,source); /* destination=RX, source=TX */
3028 refangle=ElevationAngle(destination,source);
3029 b=GetElevation(destination)+destination.alt+earthradius;
3031 fd=fopen("profile.gp","wb");
3032 fd2=fopen("reference.gp","wb");
3034 for (x=1; x<path.length-1; x++)
3036 remote.lat=path.lat[x];
3037 remote.lon=path.lon[x];
3040 a=GetElevation(remote)+earthradius;
3042 cangle=5280.0*Distance(destination,remote)/earthradius;
3044 c=b*sin(refangle*deg2rad+HALFPI)/sin(HALFPI-refangle*deg2rad-cangle);
3048 fprintf(fd,"%f\t%f\n",path.distance[x],height);
3049 fprintf(fd2,"%f\t%f\n",path.distance[x],0.);
3051 if (height>maxheight)
3054 if (height<minheight)
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);
3066 /* Default filename and output file type */
3068 strncpy(filename,"height\0",8);
3069 strncpy(term,"gif\0",4);
3070 strncpy(ext,"gif\0",4);
3075 /* Grab extension and terminal type from "name" */
3077 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
3078 filename[x]=name[x];
3082 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
3084 term[y]=tolower(name[x]);
3094 { /* No extension -- Default is gif */
3097 strncpy(term,"gif\0",4);
3098 strncpy(ext,"gif\0",4);
3102 /* Either .ps or .postscript may be used
3103 as an extension for postscript output. */
3105 if (strncmp(term,"postscript",10)==0)
3106 strncpy(ext,"ps\0",3);
3108 else if (strncmp(ext,"ps",2)==0)
3109 strncpy(term,"postscript\0",11);
3111 fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
3114 fd=fopen("splat.gp","w");
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");
3133 x=system("gnuplot splat.gp");
3138 unlink("profile.gp");
3139 unlink("reference.gp");
3140 fprintf(stdout," Done!\n");
3145 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
3148 void GraphLongley(struct site source, struct site destination, char *name)
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,
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;
3163 sprintf(report_name,"%s-to-%s.lro",source.name,destination.name);
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)
3169 fd2=fopen(report_name,"w");
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);
3176 if (source.lat>=0.0)
3178 fprintf(fd2,"Site location: %.4f North / %.4f West",source.lat, source.lon);
3179 fprintf(fd2, " (%s N / ", dec2dms(source.lat));
3185 fprintf(fd2,"Site location: %.4f South / %.4f West",-source.lat, source.lon);
3186 fprintf(fd2, " (%s S / ", dec2dms(source.lat));
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));
3196 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
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));
3201 angle=ElevationAngle(source,destination);
3204 fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle);
3207 fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle);
3209 fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
3213 fprintf(fd2,"Receiver site: %s\n",destination.name);
3215 if (destination.lat>=0.0)
3217 fprintf(fd2,"Site location: %.4f North / %.4f West",destination.lat, destination.lon);
3218 fprintf(fd2, " (%s N / ", dec2dms(destination.lat));
3223 fprintf(fd2,"Site location: %.4f South / %.4f West",-destination.lat, destination.lon);
3224 fprintf(fd2, " (%s S / ", dec2dms(destination.lat));
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));
3231 haavt=haat(destination);
3234 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
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));
3239 angle=ElevationAngle(destination,source);
3242 fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle);
3245 fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle);
3247 fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
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);
3256 switch (LR.radio_climate)
3259 fprintf(fd2,"Equatorial");
3263 fprintf(fd2,"Continental Subtropical");
3267 fprintf(fd2,"Maritime Subtropical");
3271 fprintf(fd2,"Desert");
3275 fprintf(fd2,"Continental Temperate");
3279 fprintf(fd2,"Martitime Temperate, Over Land");
3283 fprintf(fd2,"Maritime Temperate, Over Sea");
3287 fprintf(fd2,"Unknown");
3290 fprintf(fd2,")\nPolarization: %d (",LR.pol);
3293 fprintf(fd2,"Horizontal");
3296 fprintf(fd2,"Vertical");
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);
3301 fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
3303 fprintf(fd2,"Analysis Results:\n\n");
3305 ReadPath(source, destination); /* destination=RX, source=TX */
3307 elev_l[1]=0.04*METERS_PER_MILE;
3309 for (x=1; x<path.length; x++)
3310 elev_l[x+1]=path.elevation[x]*METERS_PER_FOOT;
3312 fprintf(fd2,"Distance (mi)\tLoss (dB)\tErrnum\tComment\n\n");
3314 fd=fopen("profile.gp","w");
3316 for (x=2; x<path.length; x++)
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);
3326 /* Note: PASS BY REFERENCE ... loss and errnum are
3327 passed by reference, and are only used in this
3328 file by this function */
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);
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");
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);
3366 /* Default filename and output file type */
3368 strncpy(filename,"loss\0",5);
3369 strncpy(term,"gif\0",4);
3370 strncpy(ext,"gif\0",4);
3375 /* Grab extension and terminal type from "name" */
3377 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
3378 filename[x]=name[x];
3382 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
3384 term[y]=tolower(name[x]);
3394 { /* No extension -- Default is gif */
3397 strncpy(term,"gif\0",4);
3398 strncpy(ext,"gif\0",4);
3402 /* Either .ps or .postscript may be used
3403 as an extension for postscript output. */
3405 if (strncmp(term,"postscript",10)==0)
3406 strncpy(ext,"ps\0",3);
3408 else if (strncmp(ext,"ps",2)==0)
3409 strncpy(term,"postscript\0",11);
3411 fprintf(stdout,"Writing \"%s.%s\"...",filename,ext);
3414 fd=fopen("splat.gp","w");
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");
3427 x=system("gnuplot splat.gp");
3432 unlink("profile.gp");
3433 unlink("reference.gp");
3435 fprintf(stdout," Done!\n");
3440 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
3443 void ObstructionReport(struct site xmtr, struct site rcvr, char report)
3445 struct site result, result2, new_site;
3446 double angle, haavt;
3447 unsigned char block;
3448 char report_name[80], string[255];
3452 sprintf(report_name,"%s-to-%s.txt",xmtr.name,rcvr.name);
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)
3458 fd=fopen(report_name,"w");
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);
3468 fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon);
3469 fprintf(fd, " (%s N / ", dec2dms(xmtr.lat));
3474 fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon);
3475 fprintf(fd, " (%s S / ", dec2dms(xmtr.lat));
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));
3485 fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt);
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));
3490 angle=ElevationAngle(xmtr,rcvr);
3493 fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle);
3496 fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle);
3498 fprintf(fd,"\n-------------------------------------------------------------------------\n\n");
3502 fprintf(fd,"Receiver site: %s\n",rcvr.name);
3506 fprintf(fd,"Site location: %.4f North / %.4f West",rcvr.lat, rcvr.lon);
3507 fprintf(fd, " (%s N / ", dec2dms(rcvr.lat));
3512 fprintf(fd,"Site location: %.4f South / %.4f West",-rcvr.lat, rcvr.lon);
3513 fprintf(fd, " (%s S / ", dec2dms(rcvr.lat));
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));
3523 fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt);
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));
3528 angle=ElevationAngle(rcvr,xmtr);
3531 fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle);
3534 fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle);
3536 fprintf(fd,"\n-------------------------------------------------------------------------\n\n");
3540 /* Write an Obstruction Report */
3543 result=los(xmtr,rcvr);
3546 block=result.name[0];
3549 fprintf(fd,"SPLAT! detected obstructions at:\n\n");
3553 if (result.lat!=result2.lat || result.lon!=result2.lon || result.alt!=result2.alt)
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);
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);
3565 /* Can you hear me now? :-) */
3567 result=los(xmtr,new_site);
3568 block=result.name[0];
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);
3574 sprintf(string,"\nNo obstructions due to terrain were detected by SPLAT!\n\n");
3577 fprintf(fd,"%s",string);
3581 /* Display LOS status to terminal */
3583 fprintf(stdout,"%sObstruction report written to: \"%s\"\n",string,report_name);
3587 void SiteReport(struct site xmtr)
3589 char report_name[80];
3594 sprintf(report_name,"%s-site_report.txt",xmtr.name);
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)
3600 fd=fopen(report_name,"w");
3602 fprintf(fd,"\n\t--==[ SPLAT! v%s Site Analysis Report For: %s ]==--\n\n",splat_version,xmtr.name);
3604 fprintf(fd,"---------------------------------------------------------------------------\n\n");
3608 fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon);
3609 fprintf(fd, " (%s N / ",dec2dms(xmtr.lat));
3614 fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon);
3615 fprintf(fd, " (%s S / ",dec2dms(xmtr.lat));
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));
3624 if (terrain>-4999.0)
3626 fprintf(fd,"Antenna height above average terrain: %.2f feet\n\n",terrain);
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. */
3632 for (azi=0; azi<=315; azi+=45)
3634 fprintf(fd,"Average terrain at %3d degrees azimuth: ",azi);
3635 terrain=AverageTerrain(xmtr,(double)azi,2.0,10.0);
3637 if (terrain>-4999.0)
3638 fprintf(fd,"%.2f feet AMSL\n",terrain);
3640 fprintf(fd,"No terrain\n");
3644 fprintf(fd,"\n---------------------------------------------------------------------------\n\n");
3646 fprintf(stdout,"\nSite analysis report written to: \"%s\"\n",report_name);
3649 int main(char argc, char *argv[])
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;
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,
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],
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;
3671 struct site tx_site[4], rx_site;
3675 sprintf(header,"\n\t\t--==[ Welcome To SPLAT! v%s ]==--\n\n", splat_version);
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");
3695 fprintf(stdout,"Type 'man splat', or see the documentation for more details.\n\n");
3706 elevation_file[0]=0;
3712 earthradius=EARTHRADIUS;
3716 tx_site[x].lat=91.0;
3717 tx_site[x].lon=361.0;
3720 for (x=0; x<MAXSLOTS; x++)
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;
3731 /* Scan for command line arguments */
3733 for (x=1; x<=y; x++)
3735 if (strcmp(argv[x],"-R")==0)
3739 if (z<=y && argv[z][0] && argv[z][0]!='-')
3741 sscanf(argv[z],"%lf",&max_range);
3746 if (max_range>1000.0)
3751 if (strcmp(argv[x],"-m")==0)
3755 if (z<=y && argv[z][0] && argv[z][0]!='-')
3757 sscanf(argv[z],"%lf",&er_mult);
3765 earthradius*=er_mult;
3769 if (strcmp(argv[x],"-o")==0)
3773 if (z<=y && argv[z][0] && argv[z][0]!='-')
3774 strncpy(mapfile,argv[z],253);
3778 if (strcmp(argv[x],"-c")==0)
3782 if (z<=y && argv[z][0] && argv[z][0]!='-')
3784 sscanf(argv[z],"%lf",&altitude);
3789 if (strcmp(argv[x],"-db")==0)
3793 if (z<=y && argv[z][0] && argv[z][0]!='-')
3795 sscanf(argv[z],"%d",&maxdB);
3807 if (strcmp(argv[x],"-p")==0)
3811 if (z<=y && argv[z][0] && argv[z][0]!='-')
3813 strncpy(terrain_file,argv[z],253);
3818 if (strcmp(argv[x],"-e")==0)
3822 if (z<=y && argv[z][0] && argv[z][0]!='-')
3824 strncpy(elevation_file,argv[z],253);
3829 if (strcmp(argv[x],"-h")==0)
3833 if (z<=y && argv[z][0] && argv[z][0]!='-')
3835 strncpy(height_file,argv[z],253);
3840 if (strcmp(argv[x],"-n")==0)
3842 if (z<=y && argv[z][0] && argv[z][0]!='-')
3849 if (strcmp(argv[x],"-N")==0)
3851 if (z<=y && argv[z][0] && argv[z][0]!='-');
3858 if (strcmp(argv[x],"-d")==0)
3862 if (z<=y && argv[z][0] && argv[z][0]!='-')
3863 strncpy(sdf_path,argv[z],253);
3866 if (strcmp(argv[x],"-t")==0)
3868 /* Read Transmitter Location */
3872 while (z<=y && argv[z][0] && argv[z][0]!='-' && txsites<4)
3874 strncpy(txfile,argv[z],253);
3875 tx_site[txsites]=LoadQTH(txfile);
3882 if (strcmp(argv[x],"-L")==0)
3886 if (z<=y && argv[z][0] && argv[z][0]!='-')
3888 sscanf(argv[z],"%lf",&altitudeLR);
3891 fprintf(stdout,"c and L are exclusive options, ignoring L.\n");
3900 if (strcmp(argv[x],"-l")==0)
3904 if (z<=y && argv[z][0] && argv[z][0]!='-')
3906 strncpy(longley_file,argv[z],253);
3908 /* Doing this twice is harmless */
3913 if (strcmp(argv[x],"-r")==0)
3915 /* Read Receiver Location */
3919 if (z<=y && argv[z][0] && argv[z][0]!='-')
3921 strncpy(rxfile,argv[z],253);
3922 rx_site=LoadQTH(rxfile);
3927 if (strcmp(argv[x],"-s")==0)
3929 /* Read city file(s) */
3933 while (z<=y && argv[z][0] && argv[z][0]!='-' && cities<5)
3935 strncpy(city_file[cities],argv[z],253);
3942 if (strcmp(argv[x],"-b")==0)
3944 /* Read Boundary File(s) */
3948 while (z<=y && argv[z][0] && argv[z][0]!='-' && bfs<5)
3950 strncpy(boundary_file[bfs],argv[z],253);
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. */
3965 fprintf(stderr,"\n%c*** ERROR: No transmitter site(s) specified!\n\n",7);
3969 for (x=0, y=0; x<txsites; x++)
3971 if (tx_site[x].lat==91.0 && tx_site[x].lon==361.0)
3973 fprintf(stderr,"\n*** ERROR: Transmitter site #%d not found!",x+1);
3980 fprintf(stderr,"%c\n\n",7);
3984 if ((coverage+LRmap)==0 && rx_site.lat==91.0 && rx_site.lon==361.0)
3986 fprintf(stderr,"\n%c*** ERROR: No receiver site found or specified!\n\n",7);
3990 /* No errors were detected. Whew! :-) */
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
4001 sprintf(string,"%s/.splat_path",env);
4002 fd=fopen(string,"r");
4006 fgets(string,253,fd);
4008 /* Remove <CR> and/or <LF> from string */
4010 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0 && x<253; x++);
4013 strncpy(sdf_path,string,253);
4019 /* Ensure a trailing '/' is present in sdf_path */
4025 if (sdf_path[x-1]!='/' && x!=0)
4032 fprintf(stdout,"%s",header);
4041 min_lon=(int)floor(tx_site[0].lon);
4042 max_lon=(int)floor(tx_site[0].lon);
4044 for (y=0, z=0; z<txsites; z++)
4046 txlat=(int)floor(tx_site[z].lat);
4047 txlon=(int)floor(tx_site[z].lon);
4055 if (LonDiff(txlon,min_lon)<0.0)
4058 if (LonDiff(txlon,max_lon)>0.0)
4064 rxlat=(int)floor(rx_site.lat);
4065 rxlon=(int)floor(rx_site.lon);
4073 if (LonDiff(rxlon,min_lon)<0.0)
4076 if (LonDiff(rxlon,max_lon)>0.0)
4081 /* Load the required SDF files */
4083 width=ReduceAngle(max_lon-min_lon);
4085 if ((max_lon-min_lon)<=180.0)
4087 for (y=0; y<=width; y++)
4088 for (x=min_lat; x<=max_lat; x++)
4090 ymin=(int)(min_lon+(double)y);
4106 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4113 for (y=0; y<=width; y++)
4114 for (x=min_lat; x<=max_lat; x++)
4132 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4137 if (coverage | LRmap)
4142 for (z=0; z<txsites; z++)
4144 /* "Ball park" estimates used to load any additional
4145 SDF files required to conduct this analysis. */
4147 tx_range=sqrt(1.5*(tx_site[z].alt+GetElevation(tx_site[z])));
4150 rx_range=sqrt(1.5*altitudeLR);
4152 rx_range=sqrt(1.5*altitude);
4154 /* deg_range determines the maximum
4155 amount of topo data we read */
4157 deg_range=(tx_range+rx_range)/69.0;
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
4168 max_range=tx_range+rx_range;
4170 if (max_range<(tx_range+rx_range))
4171 deg_range=max_range/69.0;
4173 /* Prevent the demand for a really wide coverage
4174 from allocating more slots than are available
4179 case 2: deg_limit=0.25;
4182 case 4: deg_limit=0.5;
4185 case 9: deg_limit=1.0;
4188 case 16: deg_limit=2.0;
4191 case 25: deg_limit=3.0;
4194 if (tx_site[z].lat<70.0)
4195 deg_range_lon=deg_range/cos(deg2rad*tx_site[z].lat);
4197 deg_range_lon=deg_range/cos(deg2rad*70.0);
4199 /* Correct for squares in degrees not being square in miles */
4201 if (deg_range>deg_limit)
4202 deg_range=deg_limit;
4204 if (deg_range_lon>deg_limit)
4205 deg_range_lon=deg_limit;
4208 north_min=(int)floor(tx_site[z].lat-deg_range);
4209 north_max=(int)floor(tx_site[z].lat+deg_range);
4211 west_min=(int)floor(tx_site[z].lon-deg_range_lon);
4216 while (west_min>=360)
4219 west_max=(int)floor(tx_site[z].lon+deg_range_lon);
4224 while (west_max>=360)
4227 if (north_min<min_lat)
4230 if (north_max>max_lat)
4233 if (LonDiff(west_min,min_lon)<0.0)
4236 if (LonDiff(west_max,max_lon)>0.0)
4241 /* Load the required SDF files */
4243 width=ReduceAngle(max_lon-min_lon);
4245 if ((max_lon-min_lon)<=180.0)
4247 for (y=0; y<=width; y++)
4248 for (x=min_lat; x<=max_lat; x++)
4250 ymin=(int)(min_lon+(double)y);
4266 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4273 for (y=0; y<=width; y++)
4274 for (x=min_lat; x<=max_lat; x++)
4276 ymin=(int)(max_lon+(double)y);
4292 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
4302 if (coverage | LRmap)
4304 for (x=0; x<txsites; x++)
4307 PlotCoverage(tx_site[x],altitude);
4310 PlotLRMap(tx_site[x],altitudeLR);
4312 PlaceMarker(tx_site[x]);
4315 SiteReport(tx_site[x]);
4321 if (coverage==0 && LRmap==0)
4323 PlaceMarker(rx_site);
4325 for (x=0; x<txsites; x++)
4327 PlaceMarker(tx_site[x]);
4334 PlotPath(tx_site[x],rx_site,1);
4338 PlotPath(tx_site[x],rx_site,8);
4342 PlotPath(tx_site[x],rx_site,16);
4346 PlotPath(tx_site[x],rx_site,32);
4351 ObstructionReport(tx_site[x],rx_site,report);
4359 for (x=0; x<bfs; x++)
4360 LoadBoundaries(boundary_file[x]);
4365 for (x=0; x<cities; x++)
4366 LoadCities(city_file[x]);
4372 WritePPMLR(mapfile);
4379 for (x=0; terrain_file[x]!='.' && terrain_file[x]!=0 && x<80; x++);
4381 if (terrain_file[x]=='.') /* extension */
4384 for (y=1, z=x, x++; terrain_file[x]!=0 && x<253 && y<14; x++, y++)
4385 ext[y]=terrain_file[x];
4393 ext[0]=0; /* No extension */
4397 for (count=0; count<txsites; count++)
4399 sprintf(string,"%s-%c%s%c",terrain_file,'1'+count,ext,0);
4400 GraphTerrain(tx_site[count],rx_site,string);
4405 GraphTerrain(tx_site[0],rx_site,terrain_file);
4412 for (x=0; elevation_file[x]!='.' && elevation_file[x]!=0 && x<80; x++);
4414 if (elevation_file[x]=='.') /* extension */
4417 for (y=1, z=x, x++; elevation_file[x]!=0 && x<253 && y<14; x++, y++)
4418 ext[y]=elevation_file[x];
4421 elevation_file[z]=0;
4426 ext[0]=0; /* No extension */
4427 elevation_file[x]=0;
4430 for (count=0; count<txsites; count++)
4432 sprintf(string,"%s-%c%s%c",elevation_file,'1'+count,ext,0);
4433 GraphElevation(tx_site[count],rx_site,string);
4438 GraphElevation(tx_site[0],rx_site,elevation_file);
4445 for (x=0; height_file[x]!='.' && height_file[x]!=0 && x<80; x++);
4447 if (height_file[x]=='.') /* extension */
4450 for (y=1, z=x, x++; height_file[x]!=0 && x<253 && y<14; x++, y++)
4451 ext[y]=height_file[x];
4459 ext[0]=0; /* No extension */
4463 for (count=0; count<txsites; count++)
4465 sprintf(string,"%s-%c%s%c",height_file,'1'+count,ext,0);
4466 GraphHeight(tx_site[count],rx_site,string);
4471 GraphHeight(tx_site[0],rx_site,height_file);
4478 for (x=0; longley_file[x]!='.' && longley_file[x]!=0 && x<80; x++);
4480 if (longley_file[x]=='.') /* extension */
4483 for (y=1, z=x, x++; longley_file[x]!=0 && x<253 && y<14; x++, y++)
4484 ext[y]=longley_file[x];
4492 ext[0]=0; /* No extension */
4496 for (count=0; count<txsites; count++)
4498 sprintf(string,"%s-%c%s%c",longley_file,'1'+count,ext,0);
4499 GraphLongley(tx_site[count],rx_site,string);
4504 GraphLongley(tx_site[0],rx_site,longley_file);