1 /****************************************************************************\
2 * SPLAT!: An RF Signal Path Loss And Terrain Analysis Tool *
3 ******************************************************************************
4 * Project started in 1997 by John A. Magliacane, KD2BD *
5 * Last update: 19-Oct-2007 *
6 ******************************************************************************
7 * Please consult the documentation for a complete list of *
8 * individuals who have contributed to this project. *
9 ******************************************************************************
11 * This program is free software; you can redistribute it and/or modify it *
12 * under the terms of the GNU General Public License as published by the *
13 * Free Software Foundation; either version 2 of the License or any later *
16 * This program is distributed in the hope that it will useful, but WITHOUT *
17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
18 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
21 ******************************************************************************
22 * g++ -Wall -O3 -s -lm -lbz2 -fomit-frame-pointer itm.cpp splat.cpp -o splat *
23 \****************************************************************************/
36 #define BZBUFFER 65536
39 #define ARRAYSIZE 4950
43 #define ARRAYSIZE 10870
47 #define ARRAYSIZE 19240
51 #define ARRAYSIZE 30025
54 char string[255], sdf_path[255], opened=0, *splat_version={"1.2.1"};
56 double TWOPI=6.283185307179586, HALFPI=1.570796326794896,
57 PI=3.141592653589793, deg2rad=1.74532925199e-02,
58 EARTHRADIUS=20902230.97, METERS_PER_MILE=1609.344,
59 METERS_PER_FOOT=0.3048, KM_PER_MILE=1.609344, earthradius,
60 max_range=0.0, forced_erp=-1.0, fzone_clearance=0.6;
62 int min_north=90, max_north=-90, min_west=360, max_west=-1,
63 max_elevation=-32768, min_elevation=32768, bzerror, maxdB=230;
65 unsigned char got_elevation_pattern, got_azimuth_pattern, metric=0;
67 struct site { double lat;
74 struct path { double lat[ARRAYSIZE];
75 double lon[ARRAYSIZE];
76 double elevation[ARRAYSIZE];
77 double distance[ARRAYSIZE];
81 struct dem { int min_north;
87 short data[1200][1200];
88 unsigned char mask[1200][1200];
89 unsigned char signal[1200][1200];
92 struct LR { double eps_dielect;
93 double sgm_conductivity;
94 double eno_ns_surfref;
101 float antenna_pattern[361][1001];
104 struct region { unsigned char color[32][3];
109 double elev_l[ARRAYSIZE+10];
111 void point_to_point(double elev[], double tht_m, double rht_m,
112 double eps_dielect, double sgm_conductivity, double eno_ns_surfref,
113 double frq_mhz, int radio_climate, int pol, double conf,
114 double rel, double &dbloss, char *strmode, int &errnum);
116 double arccos(double x, double y)
118 /* This function implements the arc cosine function,
119 returning a value between 0 and TWOPI. */
132 int ReduceAngle(double angle)
134 /* This function normalizes the argument to
135 an integer angle between 0 and 180 degrees */
139 temp=acos(cos(angle*deg2rad));
141 return (int)rint(temp/deg2rad);
144 char *dec2dms(double decimal)
146 /* Converts decimal degrees to degrees, minutes, seconds,
147 (DMS) and returns the result as a character string. */
150 int degrees, minutes, seconds;
178 sprintf(string,"%d%c %d\' %d\"", degrees*sign, 176, minutes, seconds);
182 int PutMask(double lat, double lon, int value)
184 /* Lines, text, markings, and coverage areas are stored in a
185 mask that is combined with topology data when topographic
186 maps are generated by SPLAT!. This function sets and resets
187 bits in the mask based on the latitude and longitude of the
193 for (indx=0, found=0; indx<MAXPAGES && found==0;)
194 if (lat>=(double)dem[indx].min_north && lat<=(double)dem[indx].max_north && lon>=(double)dem[indx].min_west && lon<=(double)dem[indx].max_west)
201 x=(int)(1199.0*(lat-floor(lat)));
202 y=(int)(1199.0*(lon-floor(lon)));
204 dem[indx].mask[x][y]=value;
206 return (dem[indx].mask[x][y]);
213 int OrMask(double lat, double lon, int value)
215 /* Lines, text, markings, and coverage areas are stored in a
216 mask that is combined with topology data when topographic
217 maps are generated by SPLAT!. This function sets bits in
218 the mask based on the latitude and longitude of the area
224 for (indx=0, found=0; indx<MAXPAGES && found==0;)
225 if (lat>=(double)dem[indx].min_north && lat<=(double)dem[indx].max_north && lon>=(double)dem[indx].min_west && lon<=(double)dem[indx].max_west)
232 x=(int)(1199.0*(lat-floor(lat)));
233 y=(int)(1199.0*(lon-floor(lon)));
235 dem[indx].mask[x][y]|=value;
237 return (dem[indx].mask[x][y]);
244 int GetMask(double lat, double lon)
246 /* This function returns the mask bits based on the latitude
247 and longitude given. */
249 return (OrMask(lat,lon,0));
252 int PutSignal(double lat, double lon, unsigned char signal)
254 /* This function writes a signal level (0-255)
255 at the specified location for later recall. */
260 for (indx=0, found=0; indx<MAXPAGES && found==0;)
261 if (lat>=(double)dem[indx].min_north && lat<=(double)dem[indx].max_north && lon>=(double)dem[indx].min_west && lon<=(double)dem[indx].max_west)
268 x=(int)(1199.0*(lat-floor(lat)));
269 y=(int)(1199.0*(lon-floor(lon)));
271 dem[indx].signal[x][y]=signal;
273 return (dem[indx].signal[x][y]);
280 unsigned char GetSignal(double lat, double lon)
282 /* This function reads the signal level (0-255) at the
283 specified location that was previously written by the
284 complimentary PutSignal() function. */
289 for (indx=0, found=0; indx<MAXPAGES && found==0;)
290 if (lat>=(double)dem[indx].min_north && lat<=(double)dem[indx].max_north && lon>=(double)dem[indx].min_west && lon<=(double)dem[indx].max_west)
297 x=(int)(1199.0*(lat-floor(lat)));
298 y=(int)(1199.0*(lon-floor(lon)));
300 return (dem[indx].signal[x][y]);
307 double GetElevation(struct site location)
309 /* This function returns the elevation (in feet) of any location
310 represented by the digital elevation model data in memory.
311 Function returns -5000.0 for locations not found in memory. */
319 x=(int)(1199.0*(location.lat-floor(location.lat)));
320 y=(int)(1199.0*(location.lon-floor(location.lon)));
322 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
324 if (location.lat>=(double)dem[indx].min_north && location.lat<=(double)dem[indx].max_north && location.lon>=(double)dem[indx].min_west && location.lon<=(double)dem[indx].max_west)
326 elevation=3.28084*dem[indx].data[x][y];
334 int AddElevation(double lat, double lon, double height)
336 /* This function adds a user-defined terrain feature
337 (in meters AGL) to the digital elevation model data
338 in memory. Does nothing and returns 0 for locations
339 not found in memory. */
344 x=(int)(1199.0*(lat-floor(lat)));
345 y=(int)(1199.0*(lon-floor(lon)));
347 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
349 if (lat>=(double)dem[indx].min_north && lat<=(double)dem[indx].max_north && lon>=(double)dem[indx].min_west && lon<=(double)dem[indx].max_west)
351 dem[indx].data[x][y]+=(short)rint(height);
359 double Distance(struct site site1, struct site site2)
361 /* This function returns the great circle distance
362 in miles between any two site locations. */
364 double lat1, lon1, lat2, lon2, distance;
366 lat1=site1.lat*deg2rad;
367 lon1=site1.lon*deg2rad;
368 lat2=site2.lat*deg2rad;
369 lon2=site2.lon*deg2rad;
371 distance=3959.0*acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos((lon1)-(lon2)));
376 double Azimuth(struct site source, struct site destination)
378 /* This function returns the azimuth (in degrees) to the
379 destination as seen from the location of the source. */
381 double dest_lat, dest_lon, src_lat, src_lon,
382 beta, azimuth, diff, num, den, fraction;
384 dest_lat=destination.lat*deg2rad;
385 dest_lon=destination.lon*deg2rad;
387 src_lat=source.lat*deg2rad;
388 src_lon=source.lon*deg2rad;
390 /* Calculate Surface Distance */
392 beta=acos(sin(src_lat)*sin(dest_lat)+cos(src_lat)*cos(dest_lat)*cos(src_lon-dest_lon));
394 /* Calculate Azimuth */
396 num=sin(dest_lat)-(sin(src_lat)*cos(beta));
397 den=cos(src_lat)*sin(beta);
400 /* Trap potential problems in acos() due to rounding */
408 /* Calculate azimuth */
410 azimuth=acos(fraction);
412 /* Reference it to True North */
414 diff=dest_lon-src_lon;
423 azimuth=TWOPI-azimuth;
425 return (azimuth/deg2rad);
428 double ElevationAngle(struct site source, struct site destination)
430 /* This function returns the angle of elevation (in degrees)
431 of the destination as seen from the source location.
432 A positive result represents an angle of elevation (uptilt),
433 while a negative result represents an angle of depression
434 (downtilt), as referenced to a normal to the center of
437 register double a, b, dx;
439 a=GetElevation(destination)+destination.alt+earthradius;
440 b=GetElevation(source)+source.alt+earthradius;
442 dx=5280.0*Distance(source,destination);
444 /* Apply the Law of Cosines */
446 return ((180.0*(acos(((b*b)+(dx*dx)-(a*a))/(2.0*b*dx)))/PI)-90.0);
449 void ReadPath(struct site source, struct site destination)
451 /* This function generates a sequence of latitude and
452 longitude positions between source and destination
453 locations along a great circle path, and stores
454 elevation and distance information for points
455 along that path in the "path" structure. */
458 double azimuth, distance, lat1, lon1, beta, den, num,
459 lat2, lon2, total_distance, x, y, path_length,
461 struct site tempsite;
463 lat1=source.lat*deg2rad;
464 lon1=source.lon*deg2rad;
466 lat2=destination.lat*deg2rad;
467 lon2=destination.lon*deg2rad;
469 azimuth=Azimuth(source,destination)*deg2rad;
471 total_distance=Distance(source,destination);
473 x=68755.0*acos(cos(lon1-lon2)); /* 1200 samples per degree */
474 y=68755.0*acos(cos(lat1-lat2)); /* 68755 samples per radian */
476 path_length=sqrt((x*x)+(y*y)); /* Total number of samples */
478 increment=total_distance/path_length; /* Miles per sample */
480 for (distance=0, c=0; (distance<=total_distance && c<ARRAYSIZE); distance+=increment, c++)
482 beta=distance/3959.0;
483 lat2=asin(sin(lat1)*cos(beta)+cos(azimuth)*sin(beta)*cos(lat1));
484 num=cos(beta)-(sin(lat1)*sin(lat2));
485 den=cos(lat1)*cos(lat2);
487 if (azimuth==0.0 && (beta>HALFPI-lat1))
490 else if (azimuth==HALFPI && (beta>HALFPI+lat1))
493 else if (fabs(num/den)>1.0)
498 if ((PI-azimuth)>=0.0)
499 lon2=lon1-arccos(num,den);
501 lon2=lon1+arccos(num,den);
517 path.elevation[c]=GetElevation(tempsite);
518 path.distance[c]=distance;
521 /* Make sure exact destination point is recorded at path.length-1 */
525 path.lat[c]=destination.lat;
526 path.lon[c]=destination.lon;
527 path.elevation[c]=GetElevation(destination);
528 path.distance[c]=total_distance;
535 path.length=ARRAYSIZE-1;
538 double ElevationAngle2(struct site source, struct site destination, double er)
540 /* This function returns the angle of elevation (in degrees)
541 of the destination as seen from the source location, UNLESS
542 the path between the sites is obstructed, in which case, the
543 elevation angle to the first obstruction is returned instead.
544 "er" represents the earth radius. */
548 double source_alt, destination_alt, cos_xmtr_angle,
549 cos_test_angle, test_alt, elevation, distance,
550 source_alt2, first_obstruction_angle=0.0;
555 ReadPath(source,destination);
557 distance=5280.0*Distance(source,destination);
558 source_alt=er+source.alt+GetElevation(source);
559 destination_alt=er+destination.alt+GetElevation(destination);
560 source_alt2=source_alt*source_alt;
562 /* Calculate the cosine of the elevation angle of the
563 destination (receiver) as seen by the source (transmitter). */
565 cos_xmtr_angle=((source_alt2)+(distance*distance)-(destination_alt*destination_alt))/(2.0*source_alt*distance);
567 /* Test all points in between source and destination locations to
568 see if the angle to a topographic feature generates a higher
569 elevation angle than that produced by the destination. Begin
570 at the source since we're interested in identifying the FIRST
571 obstruction along the path between source and destination. */
573 for (x=2, block=0; x<path.length && block==0; x++)
575 distance=5280.0*path.distance[x];
577 test_alt=earthradius+path.elevation[x];
579 cos_test_angle=((source_alt2)+(distance*distance)-(test_alt*test_alt))/(2.0*source_alt*distance);
581 /* Compare these two angles to determine if
582 an obstruction exists. Since we're comparing
583 the cosines of these angles rather than
584 the angles themselves, the sense of the
585 following "if" statement is reversed from
586 what it would be if the angles themselves
589 if (cos_xmtr_angle>cos_test_angle)
592 first_obstruction_angle=((acos(cos_test_angle))/deg2rad)-90.0;
597 elevation=first_obstruction_angle;
600 elevation=((acos(cos_xmtr_angle))/deg2rad)-90.0;
607 double AverageTerrain(struct site source, double azimuthx, double start_distance, double end_distance)
609 /* This function returns the average terrain calculated in
610 the direction of "azimuth" (degrees) between "start_distance"
611 and "end_distance" (miles) from the source location. If
612 the terrain is all water (non-critical error), -5000.0 is
613 returned. If not enough SDF data has been loaded into
614 memory to complete the survey (critical error), then
615 -9999.0 is returned. */
617 int c, samples, endpoint;
618 double beta, lat1, lon1, lat2, lon2, num, den, azimuth, terrain=0.0;
619 struct site destination;
621 lat1=source.lat*deg2rad;
622 lon1=source.lon*deg2rad;
624 /* Generate a path of elevations between the source
625 location and the remote location provided. */
627 beta=end_distance/3959.0;
629 azimuth=deg2rad*azimuthx;
631 lat2=asin(sin(lat1)*cos(beta)+cos(azimuth)*sin(beta)*cos(lat1));
632 num=cos(beta)-(sin(lat1)*sin(lat2));
633 den=cos(lat1)*cos(lat2);
635 if (azimuth==0.0 && (beta>HALFPI-lat1))
638 else if (azimuth==HALFPI && (beta>HALFPI+lat1))
641 else if (fabs(num/den)>1.0)
646 if ((PI-azimuth)>=0.0)
647 lon2=lon1-arccos(num,den);
649 lon2=lon1+arccos(num,den);
661 destination.lat=lat2;
662 destination.lon=lon2;
664 /* If SDF data is missing for the endpoint of
665 the radial, then the average terrain cannot
666 be accurately calculated. Return -9999.0 */
668 if (GetElevation(destination)<-4999.0)
672 ReadPath(source,destination);
674 endpoint=path.length;
676 /* Shrink the length of the radial if the
677 outermost portion is not over U.S. land. */
679 for (c=endpoint-1; c>=0 && path.elevation[c]==0.0; c--);
683 for (c=0, samples=0; c<endpoint; c++)
685 if (path.distance[c]>=start_distance)
687 terrain+=path.elevation[c];
693 terrain=-5000.0; /* No land */
695 terrain=(terrain/(double)samples);
701 double haat(struct site antenna)
703 /* This function returns the antenna's Height Above Average
704 Terrain (HAAT) based on FCC Part 73.313(d). If a critical
705 error occurs, such as a lack of SDF data to complete the
706 survey, -5000.0 is returned. */
710 double terrain, avg_terrain, haat, sum=0.0;
712 /* Calculate the average terrain between 2 and 10 miles
713 from the antenna site at azimuths of 0, 45, 90, 135,
714 180, 225, 270, and 315 degrees. */
716 for (c=0, azi=0; azi<=315 && error==0; azi+=45)
718 terrain=AverageTerrain(antenna, (double)azi, 2.0, 10.0);
720 if (terrain<-9998.0) /* SDF data is missing */
723 if (terrain>-4999.0) /* It's land, not water */
725 sum+=terrain; /* Sum of averages */
734 avg_terrain=(sum/(double)c);
735 haat=(antenna.alt+GetElevation(antenna))-avg_terrain;
740 float LonDiff(float lon1, float lon2)
742 /* This function returns the short path longitudinal
743 difference between longitude1 and longitude2
744 as an angle between -180.0 and +180.0 degrees.
745 If lon1 is west of lon2, the result is positive.
746 If lon1 is east of lon2, the result is negative. */
761 void PlaceMarker(struct site location)
763 /* This function places text and marker data in the mask array
764 for illustration on topographic maps generated by SPLAT!.
765 By default, SPLAT! centers text information BELOW the marker,
766 but may move it above, to the left, or to the right of the
767 marker depending on how much room is available on the map,
768 or depending on whether the area is already occupied by
769 another marker or label. If no room or clear space is
770 available on the map to place the marker and its associated
771 text, then the marker and text are not written to the map. */
774 char ok2print, occupied;
775 double x, y, lat, lon, textx=0.0, texty=0.0, xmin, xmax,
776 ymin, ymax, p1, p3, p6, p8, p12, p16, p24, label_length;
785 if (lat<xmax && lat>xmin && (LonDiff(lon,ymax)<0.0) && (LonDiff(lon,ymin)>0.0))
797 /* Is Marker Position Clear Of Text Or Other Markers? */
799 for (x=lat-p3; (x<=xmax && x>=xmin && x<=lat+p3); x+=p1)
800 for (y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1)
801 occupied|=(GetMask(x,y)&2);
805 /* Determine Where Text Can Be Positioned */
807 /* label_length=length in pixels.
808 Each character is 8 pixels wide. */
810 label_length=p1*(double)(strlen(location.name)<<3);
812 if ((LonDiff(lon+label_length,ymax)<=0.0) && (LonDiff(lon-label_length,ymin)>=0.0))
814 /* Default: Centered Text */
816 texty=lon+label_length/2.0;
820 /* Position Text Below The Marker */
827 /* Is This Position Clear Of
828 Text Or Other Markers? */
830 for (a=0, occupied=0; a<16; a++)
832 for (b=0; b<(int)strlen(location.name); b++)
833 for (c=0; c<8; c++, y-=p1)
834 occupied|=(GetMask(x,y)&2);
848 /* Position Text Above The Marker */
855 /* Is This Position Clear Of
856 Text Or Other Markers? */
858 for (a=0, occupied=0; a<16; a++)
860 for (b=0; b<(int)strlen(location.name); b++)
861 for (c=0; c<8; c++, y-=p1)
862 occupied|=(GetMask(x,y)&2);
877 if (LonDiff(lon-label_length,ymin)>=0.0)
879 /* Position Text To The
880 Right Of The Marker */
888 /* Is This Position Clear Of
889 Text Or Other Markers? */
891 for (a=0, occupied=0; a<16; a++)
893 for (b=0; b<(int)strlen(location.name); b++)
894 for (c=0; c<8; c++, y-=p1)
895 occupied|=(GetMask(x,y)&2);
909 /* Position Text To The
910 Left Of The Marker */
913 texty=lon+p8+(label_length);
918 /* Is This Position Clear Of
919 Text Or Other Markers? */
921 for (a=0, occupied=0; a<16; a++)
923 for (b=0; b<(int)strlen(location.name); b++)
924 for (c=0; c<8; c++, y-=p1)
925 occupied|=(GetMask(x,y)&2);
938 /* textx and texty contain the latitude and longitude
939 coordinates that describe the placement of the text
949 for (a=0; a<16 && ok2print; a++)
951 for (b=0; b<(int)strlen(location.name); b++)
953 byte=fontdata[16*(location.name[b])+a];
955 for (c=128; c>0; c=c>>1, y-=p1)
964 /* Draw Square Marker Centered
965 On Location Specified */
967 for (x=lat-p3; (x<=xmax && x>=xmin && x<=lat+p3); x+=p1)
968 for (y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1)
975 double ReadBearing(char *input)
977 /* This function takes numeric input in the form of a character
978 string, and returns an equivalent bearing in degrees as a
979 decimal number (double). The input may either be expressed
980 in decimal format (40.139722) or degree, minute, second
981 format (40 08 23). This function also safely handles
982 extra spaces found either leading, trailing, or
983 embedded within the numbers expressed in the
984 input string. Decimal seconds are permitted. */
986 double seconds, bearing=0.0;
988 int a, b, length, degrees, minutes;
990 /* Copy "input" to "string", and ignore any extra
991 spaces that might be present in the process. */
994 length=strlen(input);
996 for (a=0, b=0; a<length && a<18; a++)
998 if ((input[a]!=32 && input[a]!='\n') || (input[a]==32 && input[a+1]!=32 && input[a+1]!='\n' && b!=0))
1007 /* Count number of spaces in the clean string. */
1009 length=strlen(string);
1011 for (a=0, b=0; a<length; a++)
1015 if (b==0) /* Decimal Format (40.139722) */
1016 sscanf(string,"%lf",&bearing);
1018 if (b==2) /* Degree, Minute, Second Format (40 08 23) */
1020 sscanf(string,"%d %d %lf",°rees, &minutes, &seconds);
1022 bearing=(double)abs(degrees)+((double)abs(minutes)/60)+(fabs(seconds)/3600);
1024 if ((degrees<0) || (minutes<0) || (seconds<0.0))
1028 /* Anything else returns a 0.0 */
1030 if (bearing>360.0 || bearing<-360.0)
1036 struct site LoadQTH(char *filename)
1038 /* This function reads SPLAT! .qth (site location) files.
1039 The latitude and longitude may be expressed either in
1040 decimal degrees, or in degree, minute, second format.
1041 Antenna height is assumed to be expressed in feet above
1042 ground level (AGL), unless followed by the letter 'M',
1043 or 'm', or by the word "meters" or "Meters", in which
1044 case meters is assumed, and is handled accordingly. */
1047 char string[50], qthfile[255];
1048 struct site tempsite;
1051 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
1052 qthfile[x]=filename[x];
1064 tempsite.filename[0]=0;
1066 fd=fopen(qthfile,"r");
1071 fgets(string,49,fd);
1073 /* Strip <CR> and/or <LF> from end of site name */
1075 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; tempsite.name[x]=string[x], x++);
1080 fgets(string,49,fd);
1081 tempsite.lat=ReadBearing(string);
1083 /* Site Longitude */
1084 fgets(string,49,fd);
1085 tempsite.lon=ReadBearing(string);
1087 if (tempsite.lon<0.0)
1088 tempsite.lon+=360.0;
1090 /* Antenna Height */
1091 fgets(string,49,fd);
1094 /* Remove <CR> and/or <LF> from antenna height string */
1095 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; x++);
1099 /* Antenna height may either be in feet or meters.
1100 If the letter 'M' or 'm' is discovered in
1101 the string, then this is an indication that
1102 the value given is expressed in meters, and
1103 must be converted to feet before exiting. */
1105 for (x=0; string[x]!='M' && string[x]!='m' && string[x]!=0 && x<48; x++);
1107 if (string[x]=='M' || string[x]=='m')
1110 sscanf(string,"%f",&tempsite.alt);
1111 tempsite.alt*=3.28084;
1117 sscanf(string,"%f",&tempsite.alt);
1120 for (x=0; x<254 && qthfile[x]!=0; x++)
1121 tempsite.filename[x]=qthfile[x];
1123 tempsite.filename[x]=0;
1129 void LoadPAT(char *filename)
1131 /* This function reads and processes antenna pattern (.az
1132 and .el) files that correspond in name to previously
1133 loaded SPLAT! .lrp files. */
1135 int a, b, w, x, y, z, last_index, next_index, span;
1136 char string[255], azfile[255], elfile[255], *pointer=NULL;
1137 float az, xx, elevation, amplitude, rotation, valid1, valid2,
1138 delta, azimuth[361], azimuth_pattern[361], el_pattern[10001],
1139 elevation_pattern[361][1001], slant_angle[361], tilt,
1140 mechanical_tilt=0.0, tilt_azimuth, tilt_increment, sum;
1142 unsigned char read_count[10001];
1144 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
1146 azfile[x]=filename[x];
1147 elfile[x]=filename[x];
1162 got_azimuth_pattern=0;
1163 got_elevation_pattern=0;
1165 /* Load .az antenna pattern file */
1167 fd=fopen(azfile,"r");
1171 /* Clear azimuth pattern array */
1173 for (x=0; x<=360; x++)
1180 /* Read azimuth pattern rotation
1181 in degrees measured clockwise
1184 fgets(string,254,fd);
1185 pointer=strchr(string,';');
1190 sscanf(string,"%f",&rotation);
1193 /* Read azimuth (degrees) and corresponding
1194 normalized field radiation pattern amplitude
1195 (0.0 to 1.0) until EOF is reached. */
1197 fgets(string,254,fd);
1198 pointer=strchr(string,';');
1203 sscanf(string,"%f %f",&az, &litude);
1209 if (x>=0 && x<=360 && fd!=NULL)
1211 azimuth[x]+=amplitude;
1215 fgets(string,254,fd);
1216 pointer=strchr(string,';');
1221 sscanf(string,"%f %f",&az, &litude);
1223 } while (feof(fd)==0);
1228 /* Handle 0=360 degree ambiguity */
1230 if ((read_count[0]==0) && (read_count[360]!=0))
1232 read_count[0]=read_count[360];
1233 azimuth[0]=azimuth[360];
1236 if ((read_count[0]!=0) && (read_count[360]==0))
1238 read_count[360]=read_count[0];
1239 azimuth[360]=azimuth[0];
1242 /* Average pattern values in case more than
1243 one was read for each degree of azimuth. */
1245 for (x=0; x<=360; x++)
1247 if (read_count[x]>1)
1248 azimuth[x]/=(float)read_count[x];
1251 /* Interpolate missing azimuths
1252 to completely fill the array */
1257 for (x=0; x<=360; x++)
1259 if (read_count[x]!=0)
1267 if (last_index!=-1 && next_index!=-1)
1269 valid1=azimuth[last_index];
1270 valid2=azimuth[next_index];
1272 span=next_index-last_index;
1273 delta=(valid2-valid1)/(float)span;
1275 for (y=last_index+1; y<next_index; y++)
1276 azimuth[y]=azimuth[y-1]+delta;
1283 /* Perform azimuth pattern rotation
1284 and load azimuth_pattern[361] with
1285 azimuth pattern data in its final form. */
1287 for (x=0; x<360; x++)
1289 y=x+(int)rintf(rotation);
1294 azimuth_pattern[y]=azimuth[x];
1297 azimuth_pattern[360]=azimuth_pattern[0];
1299 got_azimuth_pattern=255;
1302 /* Read and process .el file */
1304 fd=fopen(elfile,"r");
1308 for (x=0; x<=10000; x++)
1314 /* Read mechanical tilt (degrees) and
1315 tilt azimuth in degrees measured
1316 clockwise from true North. */
1318 fgets(string,254,fd);
1319 pointer=strchr(string,';');
1324 sscanf(string,"%f %f",&mechanical_tilt, &tilt_azimuth);
1326 /* Read elevation (degrees) and corresponding
1327 normalized field radiation pattern amplitude
1328 (0.0 to 1.0) until EOF is reached. */
1330 fgets(string,254,fd);
1331 pointer=strchr(string,';');
1336 sscanf(string,"%f %f", &elevation, &litude);
1340 /* Read in normalized radiated field values
1341 for every 0.01 degrees of elevation between
1342 -10.0 and +90.0 degrees */
1344 x=(int)rintf(100.0*(elevation+10.0));
1346 if (x>=0 && x<=10000)
1348 el_pattern[x]+=amplitude;
1352 fgets(string,254,fd);
1353 pointer=strchr(string,';');
1358 sscanf(string,"%f %f", &elevation, &litude);
1363 /* Average the field values in case more than
1364 one was read for each 0.01 degrees of elevation. */
1366 for (x=0; x<=10000; x++)
1368 if (read_count[x]>1)
1369 el_pattern[x]/=(float)read_count[x];
1372 /* Interpolate between missing elevations (if
1373 any) to completely fill the array and provide
1374 radiated field values for every 0.01 degrees of
1380 for (x=0; x<=10000; x++)
1382 if (read_count[x]!=0)
1390 if (last_index!=-1 && next_index!=-1)
1392 valid1=el_pattern[last_index];
1393 valid2=el_pattern[next_index];
1395 span=next_index-last_index;
1396 delta=(valid2-valid1)/(float)span;
1398 for (y=last_index+1; y<next_index; y++)
1399 el_pattern[y]=el_pattern[y-1]+delta;
1406 /* Fill slant_angle[] array with offset angles based
1407 on the antenna's mechanical beam tilt (if any)
1408 and tilt direction (azimuth). */
1410 if (mechanical_tilt==0.0)
1412 for (x=0; x<=360; x++)
1418 tilt_increment=mechanical_tilt/90.0;
1420 for (x=0; x<=360; x++)
1423 y=(int)rintf(tilt_azimuth+xx);
1432 slant_angle[y]=-(tilt_increment*(90.0-xx));
1435 slant_angle[y]=-(tilt_increment*(xx-270.0));
1439 slant_angle[360]=slant_angle[0]; /* 360 degree wrap-around */
1441 for (w=0; w<=360; w++)
1443 tilt=slant_angle[w];
1445 /** Convert tilt angle to
1446 an array index offset **/
1448 y=(int)rintf(100.0*tilt);
1450 /* Copy shifted el_pattern[10001] field
1451 values into elevation_pattern[361][1001]
1452 at the corresponding azimuth, downsampling
1453 (averaging) along the way in chunks of 10. */
1455 for (x=y, z=0; z<=1000; x+=10, z++)
1457 for (sum=0.0, a=0; a<10; a++)
1461 if (b>=0 && b<=10000)
1466 sum+=el_pattern[10000];
1469 elevation_pattern[w][z]=sum/10.0;
1473 got_elevation_pattern=255;
1476 for (x=0; x<=360; x++)
1478 for (y=0; y<=1000; y++)
1480 if (got_elevation_pattern)
1481 elevation=elevation_pattern[x][y];
1485 if (got_azimuth_pattern)
1486 az=azimuth_pattern[x];
1490 LR.antenna_pattern[x][y]=az*elevation;
1495 int LoadSDF_SDF(char *name)
1497 /* This function reads uncompressed SPLAT Data Files (.sdf)
1498 containing digital elevation model data into memory.
1499 Elevation data, maximum and minimum elevations, and
1500 quadrangle limits are stored in the first available
1503 int x, y, data, indx, minlat, minlon, maxlat, maxlon;
1504 char found, free_page=0, line[20], sdf_file[255],
1505 path_plus_name[255];
1508 for (x=0; name[x]!='.' && name[x]!=0 && x<250; x++)
1509 sdf_file[x]=name[x];
1513 /* Parse filename for minimum latitude and longitude values */
1515 sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1523 /* Is it already in memory? */
1525 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
1527 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1531 /* Is room available to load it? */
1535 for (indx=0, free_page=0; indx<MAXPAGES && free_page==0; indx++)
1536 if (dem[indx].max_north==-90)
1542 if (free_page && found==0 && indx>=0 && indx<MAXPAGES)
1544 /* Search for SDF file in current working directory first */
1546 strncpy(path_plus_name,sdf_file,255);
1548 fd=fopen(path_plus_name,"rb");
1552 /* Next, try loading SDF file from path specified
1553 in $HOME/.splat_path file or by -d argument */
1555 strncpy(path_plus_name,sdf_path,255);
1556 strncat(path_plus_name,sdf_file,255);
1558 fd=fopen(path_plus_name,"rb");
1563 fprintf(stdout,"Loading \"%s\" into page %d...",path_plus_name,indx+1);
1567 sscanf(line,"%d",&dem[indx].max_west);
1570 sscanf(line,"%d",&dem[indx].min_north);
1573 sscanf(line,"%d",&dem[indx].min_west);
1576 sscanf(line,"%d",&dem[indx].max_north);
1578 for (x=0; x<1200; x++)
1579 for (y=0; y<1200; y++)
1584 dem[indx].data[x][y]=data;
1585 dem[indx].signal[x][y]=0;
1586 dem[indx].mask[x][y]=0;
1588 if (data>dem[indx].max_el)
1589 dem[indx].max_el=data;
1591 if (data<dem[indx].min_el)
1592 dem[indx].min_el=data;
1597 if (dem[indx].min_el<min_elevation)
1598 min_elevation=dem[indx].min_el;
1600 if (dem[indx].max_el>max_elevation)
1601 max_elevation=dem[indx].max_el;
1604 max_north=dem[indx].max_north;
1606 else if (dem[indx].max_north>max_north)
1607 max_north=dem[indx].max_north;
1610 min_north=dem[indx].min_north;
1612 else if (dem[indx].min_north<min_north)
1613 min_north=dem[indx].min_north;
1616 max_west=dem[indx].max_west;
1620 if (abs(dem[indx].max_west-max_west)<180)
1622 if (dem[indx].max_west>max_west)
1623 max_west=dem[indx].max_west;
1628 if (dem[indx].max_west<max_west)
1629 max_west=dem[indx].max_west;
1634 min_west=dem[indx].min_west;
1638 if (abs(dem[indx].min_west-min_west)<180)
1640 if (dem[indx].min_west<min_west)
1641 min_west=dem[indx].min_west;
1646 if (dem[indx].min_west>min_west)
1647 min_west=dem[indx].min_west;
1651 fprintf(stdout," Done!\n");
1664 char *BZfgets(BZFILE *bzfd, unsigned length)
1666 /* This function returns at most one less than 'length' number
1667 of characters from a bz2 compressed file whose file descriptor
1668 is pointed to by *bzfd. In operation, a buffer is filled with
1669 uncompressed data (size = BZBUFFER), which is then parsed
1670 and doled out as NULL terminated character strings every time
1671 this function is invoked. A NULL string indicates an EOF
1672 or error condition. */
1674 static int x, y, nBuf;
1675 static char buffer[BZBUFFER+1], output[BZBUFFER+1];
1678 if (opened!=1 && bzerror==BZ_OK)
1680 /* First time through. Initialize everything! */
1691 if (x==nBuf && bzerror!=BZ_STREAM_END && bzerror==BZ_OK && opened)
1693 /* Uncompress data into a static buffer */
1695 nBuf=BZ2_bzRead(&bzerror, bzfd, buffer, BZBUFFER);
1700 /* Build a string from buffer contents */
1702 output[y]=buffer[x];
1704 if (output[y]=='\n' || output[y]==0 || y==(int)length-1)
1723 int LoadSDF_BZ(char *name)
1725 /* This function reads .bz2 compressed SPLAT Data Files containing
1726 digital elevation model data into memory. Elevation data,
1727 maximum and minimum elevations, and quadrangle limits are
1728 stored in the first available dem[] structure. */
1730 int x, y, data, indx, minlat, minlon, maxlat, maxlon;
1731 char found, free_page=0, sdf_file[255], path_plus_name[255], *string;
1735 for (x=0; name[x]!='.' && name[x]!=0 && x<247; x++)
1736 sdf_file[x]=name[x];
1740 /* Parse sdf_file name for minimum latitude and longitude values */
1742 sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1754 /* Is it already in memory? */
1756 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
1758 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1762 /* Is room available to load it? */
1766 for (indx=0, free_page=0; indx<MAXPAGES && free_page==0; indx++)
1767 if (dem[indx].max_north==-90)
1773 if (free_page && found==0 && indx>=0 && indx<MAXPAGES)
1775 /* Search for SDF file in current working directory first */
1777 strncpy(path_plus_name,sdf_file,255);
1779 fd=fopen(path_plus_name,"rb");
1780 bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1782 if (fd==NULL || bzerror!=BZ_OK)
1784 /* Next, try loading SDF file from path specified
1785 in $HOME/.splat_path file or by -d argument */
1787 strncpy(path_plus_name,sdf_path,255);
1788 strncat(path_plus_name,sdf_file,255);
1790 fd=fopen(path_plus_name,"rb");
1791 bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1794 if (fd!=NULL && bzerror==BZ_OK)
1796 fprintf(stdout,"Loading \"%s\" into page %d...",path_plus_name,indx+1);
1799 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].max_west);
1800 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].min_north);
1801 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].min_west);
1802 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].max_north);
1804 for (x=0; x<1200; x++)
1805 for (y=0; y<1200; y++)
1807 string=BZfgets(bzfd,20);
1810 dem[indx].data[x][y]=data;
1811 dem[indx].signal[x][y]=0;
1812 dem[indx].mask[x][y]=0;
1814 if (data>dem[indx].max_el)
1815 dem[indx].max_el=data;
1817 if (data<dem[indx].min_el)
1818 dem[indx].min_el=data;
1823 BZ2_bzReadClose(&bzerror,bzfd);
1825 if (dem[indx].min_el<min_elevation)
1826 min_elevation=dem[indx].min_el;
1828 if (dem[indx].max_el>max_elevation)
1829 max_elevation=dem[indx].max_el;
1832 max_north=dem[indx].max_north;
1834 else if (dem[indx].max_north>max_north)
1835 max_north=dem[indx].max_north;
1838 min_north=dem[indx].min_north;
1840 else if (dem[indx].min_north<min_north)
1841 min_north=dem[indx].min_north;
1844 max_west=dem[indx].max_west;
1848 if (abs(dem[indx].max_west-max_west)<180)
1850 if (dem[indx].max_west>max_west)
1851 max_west=dem[indx].max_west;
1856 if (dem[indx].max_west<max_west)
1857 max_west=dem[indx].max_west;
1862 min_west=dem[indx].min_west;
1866 if (abs(dem[indx].min_west-min_west)<180)
1868 if (dem[indx].min_west<min_west)
1869 min_west=dem[indx].min_west;
1874 if (dem[indx].min_west>min_west)
1875 min_west=dem[indx].min_west;
1879 fprintf(stdout," Done!\n");
1892 char LoadSDF(char *name)
1894 /* This function loads the requested SDF file from the filesystem.
1895 It first tries to invoke the LoadSDF_SDF() function to load an
1896 uncompressed SDF file (since uncompressed files load slightly
1897 faster). If that attempt fails, then it tries to load a
1898 compressed SDF file by invoking the LoadSDF_BZ() function.
1899 If that fails, then we can assume that no elevation data
1900 exists for the region requested, and that the region
1901 requested must be entirely over water. */
1903 int x, y, indx, minlat, minlon, maxlat, maxlon;
1904 char found, free_page=0;
1905 int return_value=-1;
1907 /* Try to load an uncompressed SDF first. */
1909 return_value=LoadSDF_SDF(name);
1911 /* If that fails, try loading a compressed SDF. */
1913 if (return_value==0 || return_value==-1)
1914 return_value=LoadSDF_BZ(name);
1916 /* If neither format can be found, then assume the area is water. */
1918 if (return_value==0 || return_value==-1)
1920 /* Parse SDF name for minimum latitude and longitude values */
1922 sscanf(name,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1924 /* Is it already in memory? */
1926 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
1928 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1932 /* Is room available to load it? */
1936 for (indx=0, free_page=0; indx<MAXPAGES && free_page==0; indx++)
1937 if (dem[indx].max_north==-90)
1943 if (free_page && found==0 && indx>=0 && indx<MAXPAGES)
1945 fprintf(stdout,"Region \"%s\" assumed as sea-level into page %d...",name,indx+1);
1948 dem[indx].max_west=maxlon;
1949 dem[indx].min_north=minlat;
1950 dem[indx].min_west=minlon;
1951 dem[indx].max_north=maxlat;
1953 /* Fill DEM with sea-level topography */
1955 for (x=0; x<1200; x++)
1956 for (y=0; y<1200; y++)
1958 dem[indx].data[x][y]=0;
1959 dem[indx].signal[x][y]=0;
1960 dem[indx].mask[x][y]=0;
1962 if (dem[indx].min_el>0)
1966 if (dem[indx].min_el<min_elevation)
1967 min_elevation=dem[indx].min_el;
1969 if (dem[indx].max_el>max_elevation)
1970 max_elevation=dem[indx].max_el;
1973 max_north=dem[indx].max_north;
1975 else if (dem[indx].max_north>max_north)
1976 max_north=dem[indx].max_north;
1979 min_north=dem[indx].min_north;
1981 else if (dem[indx].min_north<min_north)
1982 min_north=dem[indx].min_north;
1985 max_west=dem[indx].max_west;
1989 if (abs(dem[indx].max_west-max_west)<180)
1991 if (dem[indx].max_west>max_west)
1992 max_west=dem[indx].max_west;
1997 if (dem[indx].max_west<max_west)
1998 max_west=dem[indx].max_west;
2003 min_west=dem[indx].min_west;
2007 if (abs(dem[indx].min_west-min_west)<180)
2009 if (dem[indx].min_west<min_west)
2010 min_west=dem[indx].min_west;
2015 if (dem[indx].min_west>min_west)
2016 min_west=dem[indx].min_west;
2020 fprintf(stdout," Done!\n");
2027 return return_value;
2030 void LoadCities(char *filename)
2032 /* This function reads SPLAT! city/site files, and plots
2033 the locations and names of the cities and site locations
2034 read on topographic maps generated by SPLAT! */
2037 char input[80], str[3][80];
2038 struct site city_site;
2041 fd=fopen(filename,"r");
2047 fprintf(stdout,"\nReading \"%s\"... ",filename);
2050 while (fd!=NULL && feof(fd)==0)
2052 /* Parse line for name, latitude, and longitude */
2054 for (x=0, y=0, z=0; x<78 && input[x]!=0 && z<3; x++)
2056 if (input[x]!=',' && y<78)
2070 strncpy(city_site.name,str[0],49);
2071 city_site.lat=ReadBearing(str[1]);
2072 city_site.lon=ReadBearing(str[2]);
2075 if (city_site.lon<0.0)
2076 city_site.lon+=360.0;
2078 PlaceMarker(city_site);
2084 fprintf(stdout,"Done!");
2089 fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename);
2092 void LoadUDT(char *filename)
2094 /* This function reads a file containing User-Defined Terrain
2095 features for their addition to the digital elevation model
2096 data used by SPLAT!. Elevations in the UDT file are evaluated
2097 and then copied into a temporary file under /tmp. Then the
2098 contents of the temp file are scanned, and if found to be unique,
2099 are added to the ground elevations described by the digital
2100 elevation data already loaded into memory. */
2102 int i, x, y, z, fd=0;
2103 char input[80], str[3][80], tempname[15], *pointer=NULL;
2104 double latitude, longitude, height, templat, templon,
2105 tempheight, one_pixel;
2106 FILE *fd1=NULL, *fd2=NULL;
2108 strcpy(tempname,"/tmp/XXXXXX\0");
2109 one_pixel=1.0/1200.0;
2111 fd1=fopen(filename,"r");
2115 fd=mkstemp(tempname);
2116 fd2=fopen(tempname,"w");
2118 fgets(input,78,fd1);
2120 pointer=strchr(input,';');
2125 fprintf(stdout,"\nReading \"%s\"... ",filename);
2128 while (feof(fd1)==0)
2130 /* Parse line for latitude, longitude, height */
2132 for (x=0, y=0, z=0; x<78 && input[x]!=0 && z<3; x++)
2134 if (input[x]!=',' && y<78)
2148 latitude=ReadBearing(str[0]);
2149 longitude=ReadBearing(str[1]);
2154 /* Remove <CR> and/or <LF> from antenna height string */
2156 for (i=0; str[2][i]!=13 && str[2][i]!=10 && str[2][i]!=0; i++);
2160 /* The terrain feature may be expressed in either
2161 feet or meters. If the letter 'M' or 'm' is
2162 discovered in the string, then this is an
2163 indication that the value given is expressed
2164 in meters. Otherwise the height is interpreted
2165 as being expressed in feet. */
2167 for (i=0; str[2][i]!='M' && str[2][i]!='m' && str[2][i]!=0 && i<48; i++);
2169 if (str[2][i]=='M' || str[2][i]=='m')
2172 height=rint(atof(str[2]));
2178 height=rint(3.28084*atof(str[2]));
2182 fprintf(fd2,"%f, %f, %f\n",latitude, longitude, height);
2184 fgets(input,78,fd1);
2186 pointer=strchr(input,';');
2196 fprintf(stdout,"Done!");
2199 fd1=fopen(tempname,"r");
2200 fd2=fopen(tempname,"r");
2202 fscanf(fd1,"%lf, %lf, %lf", &latitude, &longitude, &height);
2204 for (y=0; feof(fd1)==0; y++)
2208 fscanf(fd2,"%lf, %lf, %lf", &templat, &templon, &tempheight);
2210 for (x=0, z=0; feof(fd2)==0; x++)
2213 if (fabs(latitude-templat)<=one_pixel && fabs(longitude-templon)<=one_pixel)
2216 fscanf(fd2,"%lf, %lf, %lf", &templat, &templon, &tempheight);
2220 AddElevation(latitude, longitude, height);
2222 fscanf(fd1,"%lf, %lf, %lf", &latitude, &longitude, &height);
2231 fprintf(stderr,"\n*** ERROR: \"%s\": not found!",filename);
2234 void LoadBoundaries(char *filename)
2236 /* This function reads Cartographic Boundary Files available from
2237 the U.S. Census Bureau, and plots the data contained in those
2238 files on the PPM Map generated by SPLAT!. Such files contain
2239 the coordinates that describe the boundaries of cities,
2240 counties, and states. */
2243 double lat0, lon0, lat1, lon1;
2245 struct site source, destination;
2248 fd=fopen(filename,"r");
2252 fgets(string,78,fd);
2254 fprintf(stdout,"\nReading \"%s\"... ",filename);
2259 fgets(string,78,fd);
2260 sscanf(string,"%lf %lf", &lon0, &lat0);
2261 fgets(string,78,fd);
2265 sscanf(string,"%lf %lf", &lon1, &lat1);
2272 destination.lat=lat1;
2273 destination.lon=lon1;
2275 ReadPath(source,destination);
2277 for (x=0; x<path.length; x++)
2278 OrMask(path.lat[x],path.lon[x],4);
2283 fgets(string,78,fd);
2285 } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
2287 fgets(string,78,fd);
2289 } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
2293 fprintf(stdout,"Done!");
2298 fprintf(stderr,"\n*** ERROR: \"%s\": not found!",filename);
2301 char ReadLRParm(struct site txsite, char forced_read)
2303 /* This function reads Longley-Rice parameter data for the
2304 transmitter site. The file name is the same as the txsite,
2305 except the filename extension is .lrp. If the needed file
2306 is not found, then the file "splat.lrp" is read from the
2307 current working directory. Failure to load this file under
2308 a forced_read condition will result in the default parameters
2309 hard coded into this function to be used and written to
2313 char filename[255], string[80], *pointer=NULL, return_value=0;
2315 FILE *fd=NULL, *outfile=NULL;
2317 /* Default parameters */
2320 LR.sgm_conductivity=0.0;
2321 LR.eno_ns_surfref=0.0;
2329 /* Generate .lrp filename from txsite filename. */
2331 for (x=0; txsite.filename[x]!='.' && txsite.filename[x]!=0 && x<250; x++)
2332 filename[x]=txsite.filename[x];
2340 fd=fopen(filename,"r");
2344 /* Load default "splat.lrp" file */
2346 strncpy(filename,"splat.lrp\0",10);
2347 fd=fopen(filename,"r");
2352 fgets(string,80,fd);
2354 pointer=strchr(string,';');
2359 ok=sscanf(string,"%lf", &din);
2365 fgets(string,80,fd);
2367 pointer=strchr(string,';');
2372 ok=sscanf(string,"%lf", &din);
2377 LR.sgm_conductivity=din;
2379 fgets(string,80,fd);
2381 pointer=strchr(string,';');
2386 ok=sscanf(string,"%lf", &din);
2391 LR.eno_ns_surfref=din;
2393 fgets(string,80,fd);
2395 pointer=strchr(string,';');
2400 ok=sscanf(string,"%lf", &din);
2407 fgets(string,80,fd);
2409 pointer=strchr(string,';');
2414 ok=sscanf(string,"%d", &iin);
2419 LR.radio_climate=iin;
2421 fgets(string,80,fd);
2423 pointer=strchr(string,';');
2428 ok=sscanf(string,"%d", &iin);
2435 fgets(string,80,fd);
2437 pointer=strchr(string,';');
2442 ok=sscanf(string,"%lf", &din);
2449 fgets(string,80,fd);
2451 pointer=strchr(string,';');
2456 ok=sscanf(string,"%lf", &din);
2465 if (fgets(string,80,fd)!=NULL)
2467 pointer=strchr(string,':');
2472 if (sscanf(string,"%lf", &din))
2479 if (forced_erp!=-1.0)
2486 if (fd==NULL && forced_read)
2488 /* Assign some default parameters
2489 for use in this run. */
2491 LR.eps_dielect=15.0;
2492 LR.sgm_conductivity=0.005;
2493 LR.eno_ns_surfref=301.0;
2501 /* Write them to a "splat.lrp" file. */
2503 outfile=fopen("splat.lrp","w");
2505 fprintf(outfile,"%.3f\t; Earth Dielectric Constant (Relative permittivity)\n",LR.eps_dielect);
2506 fprintf(outfile,"%.3f\t; Earth Conductivity (Siemens per meter)\n", LR.sgm_conductivity);
2507 fprintf(outfile,"%.3f\t; Atmospheric Bending Constant (N-Units)\n",LR.eno_ns_surfref);
2508 fprintf(outfile,"%.3f\t; Frequency in MHz (20 MHz to 20 GHz)\n", LR.frq_mhz);
2509 fprintf(outfile,"%d\t; Radio Climate\n",LR.radio_climate);
2510 fprintf(outfile,"%d\t; Polarization (0 = Horizontal, 1 = Vertical)\n", LR.pol);
2511 fprintf(outfile,"%.2f\t; Fraction of situations\n",LR.conf);
2512 fprintf(outfile,"%.2f\t; Fraction of time\n",LR.rel);
2513 fprintf(outfile,"%.2f\t; Transmitter Effective Radiated Power in Watts (optional)\n",LR.erp);
2514 fprintf(outfile,"\nPlease consult SPLAT! documentation for the meaning and use of this data.\n");
2520 fprintf(stderr,"\n\n%c*** There were problems reading your \"%s\" file! ***\nA \"splat.lrp\" file was written to your directory with default data.\n",7,filename);
2523 else if (forced_read==0)
2526 if (forced_read && (fd==NULL || ok==0))
2528 LR.eps_dielect=15.0;
2529 LR.sgm_conductivity=0.005;
2530 LR.eno_ns_surfref=301.0;
2538 fprintf(stderr,"Longley-Rice default parameters have been assumed for this analysis.\n");
2543 return (return_value);
2546 void PlotPath(struct site source, struct site destination, char mask_value)
2548 /* This function analyzes the path between the source and
2549 destination locations. It determines which points along
2550 the path have line-of-sight visibility to the source.
2551 Points along with path having line-of-sight visibility
2552 to the source at an AGL altitude equal to that of the
2553 destination location are stored by setting bit 1 in the
2554 mask[][] array, which are displayed in green when PPM
2555 maps are later generated by SPLAT!. */
2559 register double cos_xmtr_angle, cos_test_angle, test_alt;
2560 double distance, rx_alt, tx_alt;
2562 ReadPath(source,destination);
2564 for (y=0; y<path.length; y++)
2566 /* Test this point only if it hasn't been already
2567 tested and found to be free of obstructions. */
2569 if ((GetMask(path.lat[y],path.lon[y])&mask_value)==0)
2571 distance=5280.0*path.distance[y];
2572 tx_alt=earthradius+source.alt+path.elevation[0];
2573 rx_alt=earthradius+destination.alt+path.elevation[y];
2575 /* Calculate the cosine of the elevation of the
2576 transmitter as seen at the temp rx point. */
2578 cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
2580 for (x=y, block=0; x>=0 && block==0; x--)
2582 distance=5280.0*(path.distance[y]-path.distance[x]);
2583 test_alt=earthradius+path.elevation[x];
2585 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
2587 /* Compare these two angles to determine if
2588 an obstruction exists. Since we're comparing
2589 the cosines of these angles rather than
2590 the angles themselves, the following "if"
2591 statement is reversed from what it would
2592 be if the actual angles were compared. */
2594 if (cos_xmtr_angle>cos_test_angle)
2599 OrMask(path.lat[y],path.lon[y],mask_value);
2604 void PlotLRPath(struct site source, struct site destination, unsigned char mask_value, FILE *fd)
2606 /* This function plots the RF path loss between source and
2607 destination points based on the Longley-Rice propagation
2608 model, taking into account antenna pattern data, if available. */
2610 int x, y, ifs, ofs, errnum;
2611 char block=0, strmode[100];
2612 double loss, azimuth, pattern=0.0,
2613 xmtr_alt, dest_alt, xmtr_alt2, dest_alt2,
2614 cos_rcvr_angle, cos_test_angle=0.0, test_alt,
2615 elevation=0.0, distance=0.0, four_thirds_earth,
2619 ReadPath(source,destination);
2621 four_thirds_earth=EARTHRADIUS*(4.0/3.0);
2623 /* Copy elevations along path into the elev_l[] array. */
2625 for (x=0; x<path.length; x++)
2626 elev_l[x+2]=path.elevation[x]*METERS_PER_FOOT;
2628 /* Since the only energy the Longley-Rice model considers
2629 reaching the destination is based on what is scattered
2630 or deflected from the first obstruction along the path,
2631 we first need to find the location and elevation angle
2632 of that first obstruction (if it exists). This is done
2633 using a 4/3rds Earth radius to match the model used by
2634 Longley-Rice. This information is required for properly
2635 integrating the antenna's elevation pattern into the
2636 calculation for overall path loss. (Using path.length-1
2637 below avoids a Longley-Rice model error from occuring at
2638 the destination point.) */
2640 for (y=2; (y<(path.length-1) && path.distance[y]<=max_range); y++)
2642 /* Process this point only if it
2643 has not already been processed. */
2645 if ((GetMask(path.lat[y],path.lon[y])&248)!=(mask_value<<3))
2647 distance=5280.0*path.distance[y];
2648 xmtr_alt=four_thirds_earth+source.alt+path.elevation[0];
2649 dest_alt=four_thirds_earth+destination.alt+path.elevation[y];
2650 dest_alt2=dest_alt*dest_alt;
2651 xmtr_alt2=xmtr_alt*xmtr_alt;
2653 /* Calculate the cosine of the elevation of
2654 the receiver as seen by the transmitter. */
2656 cos_rcvr_angle=((xmtr_alt2)+(distance*distance)-(dest_alt2))/(2.0*xmtr_alt*distance);
2658 if (cos_rcvr_angle>1.0)
2661 if (cos_rcvr_angle<-1.0)
2662 cos_rcvr_angle=-1.0;
2664 if (got_elevation_pattern || fd!=NULL)
2666 /* Determine the elevation angle to the first obstruction
2667 along the path IF elevation pattern data is available
2668 or an output (.plo) file has been designated. */
2670 for (x=2, block=0; (x<y && block==0); x++)
2672 distance=5280.0*path.distance[x];
2674 test_alt=four_thirds_earth+path.elevation[x];
2676 /* Calculate the cosine of the elevation
2677 angle of the terrain (test point)
2678 as seen by the transmitter. */
2680 cos_test_angle=((xmtr_alt2)+(distance*distance)-(test_alt*test_alt))/(2.0*xmtr_alt*distance);
2682 if (cos_test_angle>1.0)
2685 if (cos_test_angle<-1.0)
2686 cos_test_angle=-1.0;
2688 /* Compare these two angles to determine if
2689 an obstruction exists. Since we're comparing
2690 the cosines of these angles rather than
2691 the angles themselves, the sense of the
2692 following "if" statement is reversed from
2693 what it would be if the angles themselves
2696 if (cos_rcvr_angle>cos_test_angle)
2701 elevation=((acos(cos_test_angle))/deg2rad)-90.0;
2703 elevation=((acos(cos_rcvr_angle))/deg2rad)-90.0;
2706 /* Determine attenuation for each point along the
2707 path using Longley-Rice's point_to_point mode
2708 starting at y=2 (number_of_points = 1), the
2709 shortest distance terrain can play a role in
2712 elev_l[0]=y-1; /* (number of points - 1) */
2714 /* Distance between elevation samples */
2715 elev_l[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]);
2717 point_to_point(elev_l,source.alt*METERS_PER_FOOT,
2718 destination.alt*METERS_PER_FOOT, LR.eps_dielect,
2719 LR.sgm_conductivity, LR.eno_ns_surfref, LR.frq_mhz,
2720 LR.radio_climate, LR.pol, LR.conf, LR.rel, loss,
2723 temp.lat=path.lat[y];
2724 temp.lon=path.lon[y];
2726 azimuth=(Azimuth(source,temp));
2728 /* Write path loss data to output file */
2731 fprintf(fd,"%.7f, %.7f, %.3f, %.3f, %.2f",path.lat[y], path.lon[y], azimuth, elevation, loss);
2733 /* Integrate the antenna's radiation
2734 pattern into the overall path loss. */
2736 x=(int)rint(10.0*(10.0-elevation));
2738 if (x>=0 && x<=1000)
2740 azimuth=rint(azimuth);
2742 pattern=(double)LR.antenna_pattern[(int)azimuth][x];
2746 pattern=20.0*log10(pattern);
2749 if (fd!=NULL && (got_elevation_pattern || got_azimuth_pattern))
2750 fprintf(fd,", %.2f",loss);
2756 field_strength=(137.26+(20.0*log10(LR.frq_mhz))-loss)+(10.0*log10(LR.erp/1000.0));
2758 ifs=100+(int)rint(field_strength);
2766 ofs=GetSignal(path.lat[y],path.lon[y]);
2771 PutSignal(path.lat[y],path.lon[y],(unsigned char)ifs);
2774 fprintf(fd,", %.3f",field_strength);
2782 ifs=(int)rint(loss);
2784 ofs=GetSignal(path.lat[y],path.lon[y]);
2786 if (ofs<ifs && ofs!=0)
2789 PutSignal(path.lat[y],path.lon[y],(unsigned char)ifs);
2800 /* Mark this point as being analyzed */
2802 PutMask(path.lat[y],path.lon[y],(GetMask(path.lat[y],path.lon[y])&7)+mask_value<<3);
2807 void PlotCoverage(struct site source, double altitude)
2809 /* This function performs a 360 degree sweep around the
2810 transmitter site (source location), and plots the
2811 line-of-sight coverage of the transmitter on the SPLAT!
2812 generated topographic map based on a receiver located
2813 at the specified altitude (in feet AGL). Results
2814 are stored in memory, and written out in the form
2815 of a topographic map when the WritePPM() function
2816 is later invoked. */
2818 float lat, lon, one_pixel;
2819 static unsigned char mask_value=1;
2822 unsigned char symbol[4], x;
2824 one_pixel=1.0/1200.0;
2833 fprintf(stdout,"\n\nComputing line-of-sight coverage of \"%s\" with an RX antenna\nat %.2f %s AGL...\n\n 0%c to 25%c ",source.name,metric?altitude*METERS_PER_FOOT:altitude,metric?"meters":"feet",37,37);
2836 /* 18.75=1200 pixels/degree divided by 64 loops
2837 per progress indicator symbol (.oOo) printed. */
2839 z=(int)(18.75*ReduceAngle(max_west-min_west));
2841 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2850 PlotPath(source,edge,mask_value);
2855 fprintf(stdout,"%c",symbol[x]);
2867 fprintf(stdout,"\n25%c to 50%c ",37,37);
2870 z=(int)(18.75*(max_north-min_north));
2872 for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel)
2878 PlotPath(source,edge,mask_value);
2883 fprintf(stdout,"%c",symbol[x]);
2895 fprintf(stdout,"\n50%c to 75%c ",37,37);
2898 z=(int)(18.75*ReduceAngle(max_west-min_west));
2900 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
2909 PlotPath(source,edge,mask_value);
2914 fprintf(stdout,"%c",symbol[x]);
2926 fprintf(stdout,"\n75%c to 100%c ",37,37);
2929 z=(int)(18.75*(max_north-min_north));
2931 for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel)
2937 PlotPath(source,edge,mask_value);
2942 fprintf(stdout,"%c",symbol[x]);
2953 fprintf(stdout,"\nDone!\n");
2956 /* Assign next mask value */
2973 void PlotLRMap(struct site source, double altitude, char *plo_filename)
2975 /* This function performs a 360 degree sweep around the
2976 transmitter site (source location), and plots the
2977 Longley-Rice attenuation on the SPLAT! generated
2978 topographic map based on a receiver located at
2979 the specified altitude (in feet AGL). Results
2980 are stored in memory, and written out in the form
2981 of a topographic map when the WritePPMLR() or
2982 WritePPMSS() functions are later invoked. */
2986 float lat, lon, one_pixel;
2987 unsigned char x, symbol[4];
2988 static unsigned char mask_value=1;
2991 one_pixel=1.0/1200.0;
3000 fprintf(stdout,"\n\nComputing Longley-Rice contours of \"%s\" ", source.name);
3002 fprintf(stdout,"out to a radius\nof %.2f %s with an RX antenna at %.2f %s AGL...\n\n 0%c to 25%c ",metric?max_range*KM_PER_MILE:max_range,metric?"kilometers":"miles",metric?altitude*METERS_PER_FOOT:altitude,metric?"meters":"feet",37,37);
3005 if (plo_filename[0]!=0)
3006 fd=fopen(plo_filename,"wb");
3010 /* Write header information to output file */
3012 fprintf(fd,"%d, %d\t; max_west, min_west\n%d, %d\t; max_north, min_north\n",max_west, min_west, max_north, min_north);
3015 /* 18.75=1200 pixels/degree divided by 64 loops
3016 per progress indicator symbol (.oOo) printed. */
3018 z=(int)(18.75*ReduceAngle(max_west-min_west));
3020 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
3029 PlotLRPath(source,edge,mask_value,fd);
3034 fprintf(stdout,"%c",symbol[x]);
3046 fprintf(stdout,"\n25%c to 50%c ",37,37);
3049 z=(int)(18.75*(max_north-min_north));
3051 for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel)
3057 PlotLRPath(source,edge,mask_value,fd);
3062 fprintf(stdout,"%c",symbol[x]);
3074 fprintf(stdout,"\n50%c to 75%c ",37,37);
3077 z=(int)(18.75*ReduceAngle(max_west-min_west));
3079 for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel)
3088 PlotLRPath(source,edge,mask_value,fd);
3093 fprintf(stdout,"%c",symbol[x]);
3105 fprintf(stdout,"\n75%c to 100%c ",37,37);
3108 z=(int)(18.75*(max_north-min_north));
3110 for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel)
3116 PlotLRPath(source,edge,mask_value,fd);
3121 fprintf(stdout,"%c",symbol[x]);
3135 fprintf(stdout,"\nDone!\n");
3142 void LoadSignalColors(struct site xmtr)
3144 int x, y, ok, val[4];
3145 char filename[255], string[80], *pointer=NULL;
3148 for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++)
3149 filename[x]=xmtr.filename[x];
3157 /* Default values */
3159 region.level[0]=128;
3160 region.color[0][0]=255;
3161 region.color[0][1]=0;
3162 region.color[0][2]=0;
3164 region.level[1]=118;
3165 region.color[1][0]=255;
3166 region.color[1][1]=165;
3167 region.color[1][2]=0;
3169 region.level[2]=108;
3170 region.color[2][0]=255;
3171 region.color[2][1]=206;
3172 region.color[2][2]=0;
3175 region.color[3][0]=255;
3176 region.color[3][1]=255;
3177 region.color[3][2]=0;
3180 region.color[4][0]=184;
3181 region.color[4][1]=255;
3182 region.color[4][2]=0;
3185 region.color[5][0]=0;
3186 region.color[5][1]=255;
3187 region.color[5][2]=0;
3190 region.color[6][0]=0;
3191 region.color[6][1]=208;
3192 region.color[6][2]=0;
3195 region.color[7][0]=0;
3196 region.color[7][1]=196;
3197 region.color[7][2]=196;
3200 region.color[8][0]=0;
3201 region.color[8][1]=148;
3202 region.color[8][2]=255;
3205 region.color[9][0]=80;
3206 region.color[9][1]=80;
3207 region.color[9][2]=255;
3209 region.level[10]=28;
3210 region.color[10][0]=0;
3211 region.color[10][1]=38;
3212 region.color[10][2]=255;
3214 region.level[11]=18;
3215 region.color[11][0]=142;
3216 region.color[11][1]=63;
3217 region.color[11][2]=255;
3220 region.color[12][0]=140;
3221 region.color[12][1]=0;
3222 region.color[12][2]=128;
3226 fd=fopen("splat.scf","r");
3229 fd=fopen(filename,"r");
3233 fd=fopen(filename,"w");
3235 fprintf(fd,"; SPLAT! Auto-generated Signal Color Definition (\"%s\") File\n",filename);
3236 fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n");
3237 fprintf(fd,"; dBuV/m: red, green, blue\n;\n");
3238 fprintf(fd,"; ...where \"dBuV/m\" is the signal strength (in dBuV/m) and\n");
3239 fprintf(fd,"; \"red\", \"green\", and \"blue\" are the corresponding RGB color\n");
3240 fprintf(fd,"; definitions ranging from 0 to 255 for the region specified.\n");
3241 fprintf(fd,";\n; The following parameters may be edited and/or expanded\n");
3242 fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n");
3243 fprintf(fd,"; may be defined in this file.\n;\n;\n");
3245 for (x=0; x<region.levels; x++)
3246 fprintf(fd,"%3d: %3d, %3d, %3d\n",region.level[x], region.color[x][0], region.color[x][1], region.color[x][2]);
3254 fgets(string,80,fd);
3256 while (x<32 && feof(fd)==0)
3258 pointer=strchr(string,';');
3263 ok=sscanf(string,"%d: %d, %d, %d", &val[0], &val[1], &val[2], &val[3]);
3276 region.level[x]=val[0];
3277 region.color[x][0]=val[1];
3278 region.color[x][1]=val[2];
3279 region.color[x][2]=val[3];
3283 fgets(string,80,fd);
3291 void LoadLossColors(struct site xmtr)
3293 int x, y, ok, val[4];
3294 char filename[255], string[80], *pointer=NULL;
3297 for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++)
3298 filename[x]=xmtr.filename[x];
3306 /* Default values */
3309 region.color[0][0]=255;
3310 region.color[0][1]=0;
3311 region.color[0][2]=0;
3314 region.color[1][0]=255;
3315 region.color[1][1]=128;
3316 region.color[1][2]=0;
3318 region.level[2]=100;
3319 region.color[2][0]=255;
3320 region.color[2][1]=165;
3321 region.color[2][2]=0;
3323 region.level[3]=110;
3324 region.color[3][0]=255;
3325 region.color[3][1]=206;
3326 region.color[3][2]=0;
3328 region.level[4]=120;
3329 region.color[4][0]=255;
3330 region.color[4][1]=255;
3331 region.color[4][2]=0;
3333 region.level[5]=130;
3334 region.color[5][0]=184;
3335 region.color[5][1]=255;
3336 region.color[5][2]=0;
3338 region.level[6]=140;
3339 region.color[6][0]=0;
3340 region.color[6][1]=255;
3341 region.color[6][2]=0;
3343 region.level[7]=150;
3344 region.color[7][0]=0;
3345 region.color[7][1]=208;
3346 region.color[7][2]=0;
3348 region.level[8]=160;
3349 region.color[8][0]=0;
3350 region.color[8][1]=196;
3351 region.color[8][2]=196;
3353 region.level[9]=170;
3354 region.color[9][0]=0;
3355 region.color[9][1]=148;
3356 region.color[9][2]=255;
3358 region.level[10]=180;
3359 region.color[10][0]=80;
3360 region.color[10][1]=80;
3361 region.color[10][2]=255;
3363 region.level[11]=190;
3364 region.color[11][0]=0;
3365 region.color[11][1]=38;
3366 region.color[11][2]=255;
3368 region.level[12]=200;
3369 region.color[12][0]=142;
3370 region.color[12][1]=63;
3371 region.color[12][2]=255;
3373 region.level[13]=210;
3374 region.color[13][0]=196;
3375 region.color[13][1]=54;
3376 region.color[13][2]=255;
3378 region.level[14]=220;
3379 region.color[14][0]=255;
3380 region.color[14][1]=0;
3381 region.color[14][2]=255;
3383 region.level[15]=230;
3384 region.color[15][0]=255;
3385 region.color[15][1]=194;
3386 region.color[15][2]=204;
3390 fd=fopen("splat.lcf","r");
3393 fd=fopen(filename,"r");
3397 fd=fopen(filename,"w");
3399 fprintf(fd,"; SPLAT! Auto-generated Path-Loss Color Definition (\"%s\") File\n",filename);
3400 fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n");
3401 fprintf(fd,"; dB: red, green, blue\n;\n");
3402 fprintf(fd,"; ...where \"dB\" is the path loss (in dB) and\n");
3403 fprintf(fd,"; \"red\", \"green\", and \"blue\" are the corresponding RGB color\n");
3404 fprintf(fd,"; definitions ranging from 0 to 255 for the region specified.\n");
3405 fprintf(fd,";\n; The following parameters may be edited and/or expanded\n");
3406 fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n");
3407 fprintf(fd,"; may be defined in this file.\n;\n;\n");
3409 for (x=0; x<region.levels; x++)
3410 fprintf(fd,"%3d: %3d, %3d, %3d\n",region.level[x], region.color[x][0], region.color[x][1], region.color[x][2]);
3418 fgets(string,80,fd);
3420 while (x<32 && feof(fd)==0)
3422 pointer=strchr(string,';');
3427 ok=sscanf(string,"%d: %d, %d, %d", &val[0], &val[1], &val[2], &val[3]);
3440 region.level[x]=val[0];
3441 region.color[x][0]=val[1];
3442 region.color[x][1]=val[2];
3443 region.color[x][2]=val[3];
3447 fgets(string,80,fd);
3455 void WritePPM(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs)
3457 /* This function generates a topographic map in Portable Pix Map
3458 (PPM) format based on logarithmically scaled topology data,
3459 as well as the content of flags held in the mask[][] array.
3460 The image created is rotated counter-clockwise 90 degrees
3461 from its representation in dem[][] so that north points
3462 up and east points right in the image generated. */
3464 char mapfile[255], geofile[255], kmlfile[255];
3465 unsigned char found, mask;
3466 unsigned width, height, terrain;
3467 int indx, x, y, x0=0, y0=0;
3468 double lat, lon, one_pixel, conversion, one_over_gamma; /* USED to be float... */
3471 one_pixel=1.0/1200.0;
3472 one_over_gamma=1.0/GAMMA;
3473 conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
3475 width=(unsigned)(1200*ReduceAngle(max_west-min_west));
3476 height=(unsigned)(1200*ReduceAngle(max_north-min_north));
3479 strncpy(filename, "map.ppm\0",8);
3481 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
3483 mapfile[x]=filename[x];
3484 geofile[x]=filename[x];
3485 kmlfile[x]=filename[x];
3506 fd=fopen(geofile,"wb");
3508 fprintf(fd,"FILENAME\t%s\n",mapfile);
3509 fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
3510 fprintf(fd,"TIEPOINT\t0\t0\t%d.000\t\t%d.000\n",(max_west<180?-max_west:360-max_west),max_north);
3511 fprintf(fd,"TIEPOINT\t%u\t%u\t%d.000\t\t%d.000\n",width-1,height-1,(min_west<180?-min_west:360-min_west),min_north);
3512 fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height);
3513 fprintf(fd,"#\n# Auto Generated by SPLAT! v%s\n#\n",splat_version);
3520 fd=fopen(kmlfile,"wb");
3522 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
3523 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
3524 fprintf(fd," <Folder>\n");
3525 fprintf(fd," <name>SPLAT!</name>\n");
3526 fprintf(fd," <description>Line-of-Sight Overlay</description>\n");
3527 fprintf(fd," <GroundOverlay>\n");
3528 fprintf(fd," <name>SPLAT! Line-of-Sight Overlay</name>\n");
3529 fprintf(fd," <description>SPLAT! Coverage</description>\n");
3530 fprintf(fd," <Icon>\n");
3531 fprintf(fd," <href>%s</href>\n",mapfile);
3532 fprintf(fd," </Icon>\n");
3533 fprintf(fd," <opacity>128</opacity>\n");
3534 fprintf(fd," <LatLonBox>\n");
3535 fprintf(fd," <north>%.5f</north>\n",(double)max_north-one_pixel);
3536 fprintf(fd," <south>%.5f</south>\n",(double)min_north);
3537 fprintf(fd," <east>%.5f</east>\n",((double)min_west<180.0?(double)-min_west:360.0-(double)min_west));
3538 fprintf(fd," <west>%.5f</west>\n",(((double)max_west-one_pixel)<180.0?-((double)max_west-one_pixel):(360.0-(double)max_west-one_pixel)));
3539 fprintf(fd," <rotation>0.0</rotation>\n");
3540 fprintf(fd," </LatLonBox>\n");
3541 fprintf(fd," </GroundOverlay>\n");
3542 fprintf(fd," </Folder>\n");
3543 fprintf(fd,"</kml>\n");
3548 fd=fopen(mapfile,"wb");
3550 fprintf(fd,"P6\n%u %u\n255\n",width,height);
3551 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height);
3554 for (y=0, lat=(double)max_north-one_pixel; y<(int)height; y++, lat=(double)max_north-(one_pixel*(double)y))
3556 for (x=0, lon=(double)max_west-one_pixel; x<(int)width; x++, lon=(double)max_west-(one_pixel*(double)x))
3561 for (indx=0, found=0; indx<MAXPAGES && found==0;)
3562 if (lat>=(double)dem[indx].min_north && lat<(double)dem[indx].max_north && LonDiff(lon,(double)dem[indx].min_west)>=0.0 && LonDiff(lon,(double)dem[indx].max_west)<0.0)
3569 x0=(int)(1199.0*(lat-floor(lat)));
3570 y0=(int)(1199.0*(lon-floor(lon)));
3572 mask=dem[indx].mask[x0][y0];
3575 /* Text Labels: Red */
3576 fprintf(fd,"%c%c%c",255,0,0);
3579 /* County Boundaries: Light Cyan */
3580 fprintf(fd,"%c%c%c",128,128,255);
3582 else switch (mask&57)
3586 fprintf(fd,"%c%c%c",0,255,0);
3591 fprintf(fd,"%c%c%c",0,255,255);
3595 /* TX1 + TX2: Yellow */
3596 fprintf(fd,"%c%c%c",255,255,0);
3600 /* TX3: Medium Violet */
3601 fprintf(fd,"%c%c%c",147,112,219);
3605 /* TX1 + TX3: Pink */
3606 fprintf(fd,"%c%c%c",255,192,203);
3610 /* TX2 + TX3: Orange */
3611 fprintf(fd,"%c%c%c",255,165,0);
3615 /* TX1 + TX2 + TX3: Dark Green */
3616 fprintf(fd,"%c%c%c",0,100,0);
3621 fprintf(fd,"%c%c%c",255,130,71);
3625 /* TX1 + TX4: Green Yellow */
3626 fprintf(fd,"%c%c%c",173,255,47);
3630 /* TX2 + TX4: Dark Sea Green 1 */
3631 fprintf(fd,"%c%c%c",193,255,193);
3635 /* TX1 + TX2 + TX4: Blanched Almond */
3636 fprintf(fd,"%c%c%c",255,235,205);
3640 /* TX3 + TX4: Dark Turquoise */
3641 fprintf(fd,"%c%c%c",0,206,209);
3645 /* TX1 + TX3 + TX4: Medium Spring Green */
3646 fprintf(fd,"%c%c%c",0,250,154);
3650 /* TX2 + TX3 + TX4: Tan */
3651 fprintf(fd,"%c%c%c",210,180,140);
3655 /* TX1 + TX2 + TX3 + TX4: Gold2 */
3656 fprintf(fd,"%c%c%c",238,201,0);
3660 if (ngs) /* No terrain */
3661 fprintf(fd,"%c%c%c",255,255,255);
3664 /* Sea-level: Medium Blue */
3665 if (dem[indx].data[x0][y0]==0)
3666 fprintf(fd,"%c%c%c",0,0,170);
3669 /* Elevation: Greyscale */
3670 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
3671 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
3679 /* We should never get here, but if */
3680 /* we do, display the region as black */
3682 fprintf(fd,"%c%c%c",0,0,0);
3688 fprintf(stdout,"Done!\n");
3692 void WritePPMLR(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
3694 /* This function generates a topographic map in Portable Pix Map
3695 (PPM) format based on the content of flags held in the mask[][]
3696 array (only). The image created is rotated counter-clockwise
3697 90 degrees from its representation in dem[][] so that north
3698 points up and east points right in the image generated. */
3700 char mapfile[255], geofile[255], kmlfile[255], color=0;
3701 unsigned width, height, red, green, blue, terrain=0;
3702 unsigned char found, mask, cityorcounty;
3703 int indx, x, y, z, colorwidth, x0, y0, loss, level,
3704 hundreds, tens, units, match;
3705 double lat, lon, one_pixel, conversion, one_over_gamma;
3708 one_pixel=1.0/1200.0;
3709 one_over_gamma=1.0/GAMMA;
3710 conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
3712 width=(unsigned)(1200*ReduceAngle(max_west-min_west));
3713 height=(unsigned)(1200*ReduceAngle(max_north-min_north));
3715 LoadLossColors(xmtr[0]);
3718 strncpy(filename, xmtr[0].filename,254);
3720 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
3722 mapfile[x]=filename[x];
3723 geofile[x]=filename[x];
3724 kmlfile[x]=filename[x];
3745 fd=fopen(geofile,"wb");
3747 fprintf(fd,"FILENAME\t%s\n",mapfile);
3748 fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
3749 fprintf(fd,"TIEPOINT\t0\t0\t%d.000\t\t%d.000\n",(max_west<180?-max_west:360-max_west),max_north);
3750 fprintf(fd,"TIEPOINT\t%u\t%u\t%d.000\t\t%.3f\n",width-1,height+29,(min_west<180?-min_west:360-min_west),(double)(min_north-0.025));
3751 fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height+30);
3752 fprintf(fd,"#\n# Auto Generated by SPLAT! v%s\n#\n",splat_version);
3759 fd=fopen(kmlfile,"wb");
3761 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
3762 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
3763 fprintf(fd,"<!-- Generated by SPLAT! Version %s -->\n",splat_version);
3764 fprintf(fd," <Folder>\n");
3765 fprintf(fd," <name>SPLAT!</name>\n");
3766 fprintf(fd," <description>%s Transmitter Path Loss Overlay</description>\n",xmtr[0].name);
3767 fprintf(fd," <GroundOverlay>\n");
3768 fprintf(fd," <name>SPLAT! Path Loss Overlay</name>\n");
3769 fprintf(fd," <description>SPLAT! Coverage</description>\n");
3770 fprintf(fd," <Icon>\n");
3771 fprintf(fd," <href>%s</href>\n",mapfile);
3772 fprintf(fd," </Icon>\n");
3773 fprintf(fd," <opacity>128</opacity>\n");
3774 fprintf(fd," <LatLonBox>\n");
3775 fprintf(fd," <north>%.5f</north>\n",(double)max_north-one_pixel);
3776 fprintf(fd," <south>%.5f</south>\n",(double)min_north);
3777 fprintf(fd," <east>%.5f</east>\n",((double)min_west<180.0?(double)-min_west:360.0-(double)min_west));
3778 fprintf(fd," <west>%.5f</west>\n",(((double)max_west-one_pixel)<180.0?-((double)max_west-one_pixel):(360.0-(double)max_west-one_pixel)));
3779 fprintf(fd," <rotation>0.0</rotation>\n");
3780 fprintf(fd," </LatLonBox>\n");
3781 fprintf(fd," </GroundOverlay>\n");
3783 for (x=0; x<txsites; x++)
3785 fprintf(fd," <Placemark>\n");
3786 fprintf(fd," <name>%s</name>\n",xmtr[x].name);
3787 fprintf(fd," <visibility>1</visibility>\n");
3788 fprintf(fd," <Style>\n");
3789 fprintf(fd," <IconStyle>\n");
3790 fprintf(fd," <Icon>\n");
3791 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
3792 fprintf(fd," <x>224</x>\n");
3793 fprintf(fd," <y>224</y>\n");
3794 fprintf(fd," <w>32</w>\n");
3795 fprintf(fd," <h>32</h>\n");
3796 fprintf(fd," </Icon>\n");
3797 fprintf(fd," </IconStyle>\n");
3798 fprintf(fd," </Style>\n");
3799 fprintf(fd," <Point>\n");
3800 fprintf(fd," <extrude>1</extrude>\n");
3801 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
3802 fprintf(fd," <coordinates>%f,%f,%f</coordinates>\n",(xmtr[x].lon<180.0?-xmtr[x].lon:360.0-xmtr[x].lon), xmtr[x].lat, xmtr[x].alt);
3803 fprintf(fd," </Point>\n");
3804 fprintf(fd," </Placemark>\n");
3807 fprintf(fd," </Folder>\n");
3808 fprintf(fd,"</kml>\n");
3813 fd=fopen(mapfile,"wb");
3815 fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
3816 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
3819 for (y=0, lat=(double)max_north-one_pixel; y<(int)height; y++, lat=(double)max_north-(one_pixel*(double)y))
3821 for (x=0, lon=(double)max_west-one_pixel; x<(int)width; x++, lon=(double)max_west-(one_pixel*(double)x))
3826 for (indx=0, found=0; indx<MAXPAGES && found==0;)
3827 if (lat>=(double)dem[indx].min_north && lat<(double)dem[indx].max_north && LonDiff(lon,(double)dem[indx].min_west)>=0.0 && LonDiff(lon,(double)dem[indx].max_west)<0.0)
3834 x0=(int)(1199.0*(lat-floor(lat)));
3835 y0=(int)(1199.0*(lon-floor(lon)));
3837 mask=dem[indx].mask[x0][y0];
3838 loss=(dem[indx].signal[x0][y0]);
3847 if (loss<=region.level[0])
3851 for (z=1; (z<region.levels && match==255); z++)
3853 if (loss>=region.level[z-1] && loss<region.level[z])
3858 if (match<region.levels)
3860 red=region.color[match][0];
3861 green=region.color[match][1];
3862 blue=region.color[match][2];
3867 if ((mask&2) && (kml==0))
3869 /* Text Labels: Red or otherwise */
3871 if (red>=180 && green<=75 && blue<=75 && loss!=0)
3872 fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
3874 fprintf(fd,"%c%c%c",255,0,0);
3879 else if ((mask&4) && (kml==0))
3881 /* County Boundaries: Black */
3883 fprintf(fd,"%c%c%c",0,0,0);
3888 if (cityorcounty==0)
3890 if (loss>maxdB || loss==0)
3892 if (ngs) /* No terrain */
3893 fprintf(fd,"%c%c%c",255,255,255);
3896 /* Display land or sea elevation */
3898 if (dem[indx].data[x0][y0]==0)
3899 fprintf(fd,"%c%c%c",0,0,170);
3902 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
3903 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
3910 /* Plot path loss in color */
3912 if (red!=0 || green!=0 || blue!=0)
3913 fprintf(fd,"%c%c%c",red,green,blue);
3915 else /* terrain / sea-level */
3917 if (dem[indx].data[x0][y0]==0)
3918 fprintf(fd,"%c%c%c",0,0,170);
3921 /* Elevation: Greyscale */
3922 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
3923 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
3932 /* We should never get here, but if */
3933 /* we do, display the region as black */
3935 fprintf(fd,"%c%c%c",0,0,0);
3940 if (kml==0 && color)
3942 /* Display legend along bottom of image */
3944 colorwidth=(int)rint((float)width/(float)region.levels);
3946 for (y0=0; y0<30; y0++)
3948 for (x0=0; x0<(int)width; x0++)
3952 level=region.level[indx];
3957 level-=(hundreds*100);
3966 if (y0>=8 && y0<=23)
3971 if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-11)))
3975 if (tens>0 || hundreds>0)
3978 if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-19)))
3983 if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-27)))
3987 if (fontdata[16*('d')+(y0-8)]&(128>>(x-42)))
3991 if (fontdata[16*('B')+(y0-8)]&(128>>(x-50)))
3995 if (indx>region.levels)
3996 fprintf(fd,"%c%c%c",0,0,0);
3999 red=region.color[indx][0];
4000 green=region.color[indx][1];
4001 blue=region.color[indx][2];
4003 fprintf(fd,"%c%c%c",red,green,blue);
4010 fprintf(stdout,"Done!\n");
4014 void WritePPMSS(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
4016 /* This function generates a topographic map in Portable Pix Map
4017 (PPM) format based on the signal strength values held in the
4018 signal[][] array. The image created is rotated counter-clockwise
4019 90 degrees from its representation in dem[][] so that north
4020 points up and east points right in the image generated. */
4022 char mapfile[255], geofile[255], kmlfile[255], color=0;
4023 unsigned width, height, terrain, red, green, blue;
4024 unsigned char found, mask, cityorcounty;
4025 int indx, x, y, z=1, x0, y0, signal, level, hundreds,
4026 tens, units, match, colorwidth;
4027 double lat, lon, one_pixel, conversion, one_over_gamma;
4030 one_pixel=1.0/1200.0;
4031 one_over_gamma=1.0/GAMMA;
4032 conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
4034 width=(unsigned)(1200*ReduceAngle(max_west-min_west));
4035 height=(unsigned)(1200*ReduceAngle(max_north-min_north));
4037 LoadSignalColors(xmtr[0]);
4040 strncpy(filename, xmtr[0].filename,254);
4042 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
4044 mapfile[x]=filename[x];
4045 geofile[x]=filename[x];
4046 kmlfile[x]=filename[x];
4067 fd=fopen(geofile,"wb");
4069 fprintf(fd,"FILENAME\t%s\n",mapfile);
4070 fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
4071 fprintf(fd,"TIEPOINT\t0\t0\t%d.000\t\t%d.000\n",(max_west<180?-max_west:360-max_west),max_north);
4072 fprintf(fd,"TIEPOINT\t%u\t%u\t%d.000\t\t%.3f\n",width-1,height+29,(min_west<180?-min_west:360-min_west),(double)(min_north-0.025));
4073 fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height+30);
4074 fprintf(fd,"#\n# Auto Generated by SPLAT! v%s\n#\n",splat_version);
4081 fd=fopen(kmlfile,"wb");
4083 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
4084 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
4085 fprintf(fd,"<!-- Generated by SPLAT! Version %s -->\n",splat_version);
4086 fprintf(fd," <Folder>\n");
4087 fprintf(fd," <name>SPLAT!</name>\n");
4088 fprintf(fd," <description>%s Transmitter Coverage Overlay</description>\n",xmtr[0].name);
4089 fprintf(fd," <GroundOverlay>\n");
4090 fprintf(fd," <name>SPLAT! Signal Strength Overlay</name>\n");
4091 fprintf(fd," <description>SPLAT! Coverage</description>\n");
4092 fprintf(fd," <Icon>\n");
4093 fprintf(fd," <href>%s</href>\n",mapfile);
4094 fprintf(fd," </Icon>\n");
4095 fprintf(fd," <opacity>128</opacity>\n");
4096 fprintf(fd," <LatLonBox>\n");
4097 fprintf(fd," <north>%.5f</north>\n",(double)max_north-one_pixel);
4098 fprintf(fd," <south>%.5f</south>\n",(double)min_north);
4099 fprintf(fd," <east>%.5f</east>\n",((double)min_west<180.0?(double)-min_west:360.0-(double)min_west));
4100 fprintf(fd," <west>%.5f</west>\n",(((double)max_west-one_pixel)<180.0?-((double)max_west-one_pixel):(360.0-(double)max_west-one_pixel)));
4101 fprintf(fd," <rotation>0.0</rotation>\n");
4102 fprintf(fd," </LatLonBox>\n");
4103 fprintf(fd," </GroundOverlay>\n");
4105 for (x=0; x<txsites; x++)
4107 fprintf(fd," <Placemark>\n");
4108 fprintf(fd," <name>%s</name>\n",xmtr[x].name);
4109 fprintf(fd," <visibility>1</visibility>\n");
4110 fprintf(fd," <Style>\n");
4111 fprintf(fd," <IconStyle>\n");
4112 fprintf(fd," <Icon>\n");
4113 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
4114 fprintf(fd," <x>224</x>\n");
4115 fprintf(fd," <y>224</y>\n");
4116 fprintf(fd," <w>32</w>\n");
4117 fprintf(fd," <h>32</h>\n");
4118 fprintf(fd," </Icon>\n");
4119 fprintf(fd," </IconStyle>\n");
4120 fprintf(fd," </Style>\n");
4121 fprintf(fd," <Point>\n");
4122 fprintf(fd," <extrude>1</extrude>\n");
4123 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
4124 fprintf(fd," <coordinates>%f,%f,%f</coordinates>\n",(xmtr[x].lon<180.0?-xmtr[x].lon:360.0-xmtr[x].lon), xmtr[x].lat, xmtr[x].alt);
4125 fprintf(fd," </Point>\n");
4126 fprintf(fd," </Placemark>\n");
4129 fprintf(fd," </Folder>\n");
4130 fprintf(fd,"</kml>\n");
4135 fd=fopen(mapfile,"wb");
4137 fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
4138 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
4141 for (y=0, lat=(double)max_north-one_pixel; y<(int)height; y++, lat=(double)max_north-(one_pixel*(double)y))
4143 for (x=0, lon=(double)max_west-one_pixel; x<(int)width; x++, lon=(double)max_west-(one_pixel*(double)x))
4148 for (indx=0, found=0; indx<MAXPAGES && found==0;)
4149 if (lat>=(double)dem[indx].min_north && lat<(double)dem[indx].max_north && LonDiff(lon,(double)dem[indx].min_west)>=0.0 && LonDiff(lon,(double)dem[indx].max_west)<0.0)
4156 x0=(int)(1199.0*(lat-floor(lat)));
4157 y0=(int)(1199.0*(lon-floor(lon)));
4159 mask=dem[indx].mask[x0][y0];
4160 signal=(dem[indx].signal[x0][y0])-100;
4169 if (signal>=region.level[0])
4173 for (z=1; (z<region.levels && match==255); z++)
4175 if (signal<region.level[z-1] && signal>=region.level[z])
4180 if (match<region.levels)
4182 red=region.color[match][0];
4183 green=region.color[match][1];
4184 blue=region.color[match][2];
4189 if ((mask&2) && (kml==0))
4191 /* Text Labels: Red or otherwise */
4193 if (red>=180 && green<=75 && blue<=75)
4194 fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
4196 fprintf(fd,"%c%c%c",255,0,0);
4201 else if ((mask&4) && (kml==0))
4203 /* County Boundaries: Black */
4205 fprintf(fd,"%c%c%c",0,0,0);
4210 if (cityorcounty==0)
4212 if (dem[indx].signal[x0][y0]==0)
4215 fprintf(fd,"%c%c%c",255,255,255);
4218 /* Display land or sea elevation */
4220 if (dem[indx].data[x0][y0]==0)
4221 fprintf(fd,"%c%c%c",0,0,170);
4224 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
4225 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
4232 /* Plot field strength regions in color */
4234 if (red!=0 || green!=0 || blue!=0)
4235 fprintf(fd,"%c%c%c",red,green,blue);
4237 else /* terrain / sea-level */
4240 fprintf(fd,"%c%c%c",255,255,255);
4243 if (dem[indx].data[x0][y0]==0)
4244 fprintf(fd,"%c%c%c",0,0,170);
4247 /* Elevation: Greyscale */
4248 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
4249 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
4259 /* We should never get here, but if */
4260 /* we do, display the region as black */
4262 fprintf(fd,"%c%c%c",0,0,0);
4267 if (kml==0 && color)
4269 /* Display legend along bottom of image */
4271 colorwidth=(int)rint((float)width/(float)region.levels);
4273 for (y0=0; y0<30; y0++)
4275 for (x0=0; x0<(int)width; x0++)
4279 level=region.level[indx];
4284 level-=(hundreds*100);
4293 if (y0>=8 && y0<=23)
4298 if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-5)))
4302 if (tens>0 || hundreds>0)
4305 if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-13)))
4310 if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-21)))
4314 if (fontdata[16*('d')+(y0-8)]&(128>>(x-36)))
4318 if (fontdata[16*('B')+(y0-8)]&(128>>(x-44)))
4322 if (fontdata[16*('u')+(y0-8)]&(128>>(x-52)))
4326 if (fontdata[16*('V')+(y0-8)]&(128>>(x-60)))
4330 if (fontdata[16*('/')+(y0-8)]&(128>>(x-68)))
4334 if (fontdata[16*('m')+(y0-8)]&(128>>(x-76)))
4338 if (indx>region.levels)
4339 fprintf(fd,"%c%c%c",0,0,0);
4342 red=region.color[indx][0];
4343 green=region.color[indx][1];
4344 blue=region.color[indx][2];
4346 fprintf(fd,"%c%c%c",red,green,blue);
4353 fprintf(stdout,"Done!\n");
4357 void GraphTerrain(struct site source, struct site destination, char *name)
4359 /* This function invokes gnuplot to generate an appropriate
4360 output file indicating the terrain profile between the source
4361 and destination locations. "filename" is the name assigned
4362 to the output file generated by gnuplot. The filename extension
4363 is used to set gnuplot's terminal setting and output file type.
4364 If no extension is found, .png is assumed. */
4367 char filename[255], term[30], ext[15];
4370 ReadPath(destination,source);
4372 fd=fopen("profile.gp","wb");
4374 for (x=0; x<path.length; x++)
4377 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*path.elevation[x]);
4379 fprintf(fd,"%f\t%f\n",path.distance[x],path.elevation[x]);
4386 /* Default filename and output file type */
4388 strncpy(filename,"profile\0",8);
4389 strncpy(term,"png\0",4);
4390 strncpy(ext,"png\0",4);
4395 /* Grab extension and terminal type from "name" */
4397 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
4398 filename[x]=name[x];
4402 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
4404 term[y]=tolower(name[x]);
4414 { /* No extension -- Default is png */
4417 strncpy(term,"png\0",4);
4418 strncpy(ext,"png\0",4);
4422 /* Either .ps or .postscript may be used
4423 as an extension for postscript output. */
4425 if (strncmp(term,"postscript",10)==0)
4426 strncpy(ext,"ps\0",3);
4428 else if (strncmp(ext,"ps",2)==0)
4429 strncpy(term,"postscript enhanced color\0",26);
4431 fd=fopen("splat.gp","w");
4432 fprintf(fd,"set grid\n");
4433 fprintf(fd,"set autoscale\n");
4434 fprintf(fd,"set encoding iso_8859_1\n");
4435 fprintf(fd,"set term %s\n",term);
4436 fprintf(fd,"set title \"SPLAT! Terrain Profile Between %s and %s (%.2f%c Azimuth)\"\n",destination.name, source.name, Azimuth(destination,source),176);
4440 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination));
4441 fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (meters)\"\n");
4448 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination));
4449 fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (feet)\"\n");
4452 fprintf(fd,"set output \"%s.%s\"\n",filename,ext);
4453 fprintf(fd,"plot \"profile.gp\" title \"\" with lines\n");
4456 x=system("gnuplot splat.gp");
4461 unlink("profile.gp");
4463 fprintf(stdout,"\nTerrain plot written to: \"%s.%s\"",filename,ext);
4468 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
4471 void GraphElevation(struct site source, struct site destination, char *name)
4473 /* This function invokes gnuplot to generate an appropriate
4474 output file indicating the terrain profile between the source
4475 and destination locations. "filename" is the name assigned
4476 to the output file generated by gnuplot. The filename extension
4477 is used to set gnuplot's terminal setting and output file type.
4478 If no extension is found, .png is assumed. */
4481 char filename[255], term[30], ext[15];
4482 double angle, refangle, maxangle=-90.0;
4484 FILE *fd=NULL, *fd2=NULL;
4486 ReadPath(destination,source); /* destination=RX, source=TX */
4487 refangle=ElevationAngle(destination,source);
4489 fd=fopen("profile.gp","wb");
4490 fd2=fopen("reference.gp","wb");
4492 for (x=1; x<path.length-1; x++)
4494 remote.lat=path.lat[x];
4495 remote.lon=path.lon[x];
4497 angle=ElevationAngle(destination,remote);
4501 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],angle);
4502 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[x],refangle);
4507 fprintf(fd,"%f\t%f\n",path.distance[x],angle);
4508 fprintf(fd2,"%f\t%f\n",path.distance[x],refangle);
4517 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],refangle);
4518 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],refangle);
4523 fprintf(fd,"%f\t%f\n",path.distance[path.length-1],refangle);
4524 fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],refangle);
4532 /* Default filename and output file type */
4534 strncpy(filename,"profile\0",8);
4535 strncpy(term,"png\0",4);
4536 strncpy(ext,"png\0",4);
4541 /* Grab extension and terminal type from "name" */
4543 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
4544 filename[x]=name[x];
4548 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
4550 term[y]=tolower(name[x]);
4560 { /* No extension -- Default is png */
4563 strncpy(term,"png\0",4);
4564 strncpy(ext,"png\0",4);
4568 /* Either .ps or .postscript may be used
4569 as an extension for postscript output. */
4571 if (strncmp(term,"postscript",10)==0)
4572 strncpy(ext,"ps\0",3);
4574 else if (strncmp(ext,"ps",2)==0)
4575 strncpy(term,"postscript enhanced color\0",26);
4577 fd=fopen("splat.gp","w");
4579 fprintf(fd,"set grid\n");
4580 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", (-fabs(refangle)-0.25), maxangle+0.25);
4581 fprintf(fd,"set encoding iso_8859_1\n");
4582 fprintf(fd,"set term %s\n",term);
4583 fprintf(fd,"set title \"SPLAT! Elevation Profile Between %s and %s (%.2f%c azimuth)\"\n",destination.name,source.name,Azimuth(destination,source),176);
4586 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination));
4588 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination));
4591 fprintf(fd,"set ylabel \"Elevation Angle Along LOS Path Between %s and %s (degrees)\"\n",destination.name,source.name);
4592 fprintf(fd,"set output \"%s.%s\"\n",filename,ext);
4593 fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"reference.gp\" title \"Line of Sight Path (%.2f%c elevation)\" with lines\n",refangle,176);
4597 x=system("gnuplot splat.gp");
4602 unlink("profile.gp");
4603 unlink("reference.gp");
4605 fprintf(stdout,"\nElevation plot written to: \"%s.%s\"",filename,ext);
4610 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
4613 void GraphHeight(struct site source, struct site destination, char *name, double f, unsigned char n)
4615 /* This function invokes gnuplot to generate an appropriate
4616 output file indicating the terrain profile between the source
4617 and destination locations referenced to the line-of-sight path
4618 between the receive and transmit sites. "filename" is the name
4619 assigned to the output file generated by gnuplot. The filename
4620 extension is used to set gnuplot's terminal setting and output
4621 file type. If no extension is found, .png is assumed. */
4624 char filename[255], term[30], ext[15];
4625 double a, b, c, height=0.0, refangle, cangle, maxheight=-100000.0,
4626 minheight=100000.0, lambda=0.0, f_zone=0.0, fpt6_zone=0.0,
4627 nm=0.0, nb=0.0, ed=0.0, es=0.0, r=0.0, d=0.0, d1=0.0,
4628 terrain, azimuth, distance, dheight=0.0, minterrain=100000.0,
4629 minearth=100000.0, miny, maxy, min2y, max2y;
4631 FILE *fd=NULL, *fd2=NULL, *fd3=NULL, *fd4=NULL, *fd5=NULL;
4633 ReadPath(destination,source); /* destination=RX, source=TX */
4634 azimuth=Azimuth(destination,source);
4635 distance=Distance(destination,source);
4636 refangle=ElevationAngle(destination,source);
4637 b=GetElevation(destination)+destination.alt+earthradius;
4639 /* Wavelength and path distance (great circle) in feet. */
4643 lambda=9.8425e8/(f*1e6);
4644 d=5280.0*path.distance[path.length-1];
4649 ed=GetElevation(destination);
4650 es=GetElevation(source);
4651 nb=-destination.alt-ed;
4652 nm=(-source.alt-es-nb)/(path.distance[path.length-1]);
4655 fd=fopen("profile.gp","wb");
4656 fd2=fopen("reference.gp","wb");
4657 fd5=fopen("curvature.gp", "wb");
4661 fd3=fopen("fresnel.gp", "wb");
4662 fd4=fopen("fresnel_pt_6.gp", "wb");
4665 for (x=0; x<path.length-1; x++)
4667 remote.lat=path.lat[x];
4668 remote.lon=path.lon[x];
4671 terrain=GetElevation(remote);
4674 terrain+=destination.alt; /* RX antenna spike */
4676 a=terrain+earthradius;
4677 cangle=5280.0*Distance(destination,remote)/earthradius;
4678 c=b*sin(refangle*deg2rad+HALFPI)/sin(HALFPI-refangle*deg2rad-cangle);
4682 /* Per Fink and Christiansen, Electronics
4683 * Engineers' Handbook, 1989:
4685 * H = sqrt(lamba * d1 * (d - d1)/d)
4687 * where H is the distance from the LOS
4688 * path to the first Fresnel zone boundary.
4693 d1=5280.0*path.distance[x];
4694 f_zone=-1.0*sqrt(lambda*d1*(d-d1)/d);
4695 fpt6_zone=f_zone*fzone_clearance;
4700 r=-(nm*path.distance[x])-nb;
4715 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*height);
4716 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*r);
4717 fprintf(fd5,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*(height-terrain));
4722 fprintf(fd,"%f\t%f\n",path.distance[x],height);
4723 fprintf(fd2,"%f\t%f\n",path.distance[x],r);
4724 fprintf(fd5,"%f\t%f\n",path.distance[x],height-terrain);
4731 fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*f_zone);
4732 fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*fpt6_zone);
4737 fprintf(fd3,"%f\t%f\n",path.distance[x],f_zone);
4738 fprintf(fd4,"%f\t%f\n",path.distance[x],fpt6_zone);
4741 if (f_zone<minheight)
4745 if (height>maxheight)
4748 if (height<minheight)
4754 if (terrain<minterrain)
4757 if ((height-terrain)<minearth)
4758 minearth=height-terrain;
4762 r=-(nm*path.distance[path.length-1])-nb;
4768 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
4769 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
4774 fprintf(fd,"%f\t%f\n",path.distance[path.length-1],r);
4775 fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],r);
4782 fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
4783 fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
4788 fprintf(fd3,"%f\t%f\n",path.distance[path.length-1],r);
4789 fprintf(fd4,"%f\t%f\n",path.distance[path.length-1],r);
4811 /* Default filename and output file type */
4813 strncpy(filename,"height\0",8);
4814 strncpy(term,"png\0",4);
4815 strncpy(ext,"png\0",4);
4820 /* Grab extension and terminal type from "name" */
4822 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
4823 filename[x]=name[x];
4827 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
4829 term[y]=tolower(name[x]);
4839 { /* No extension -- Default is png */
4842 strncpy(term,"png\0",4);
4843 strncpy(ext,"png\0",4);
4847 /* Either .ps or .postscript may be used
4848 as an extension for postscript output. */
4850 if (strncmp(term,"postscript",10)==0)
4851 strncpy(ext,"ps\0",3);
4853 else if (strncmp(ext,"ps",2)==0)
4854 strncpy(term,"postscript enhanced color\0",26);
4856 fd=fopen("splat.gp","w");
4858 dheight=maxheight-minheight;
4859 miny=minheight-0.15*dheight;
4860 maxy=maxheight+0.05*dheight;
4865 dheight=maxheight-minheight;
4866 min2y=miny-minterrain+0.05*dheight;
4870 miny-=min2y-minearth+0.05*dheight;
4871 min2y=minearth-0.05*dheight;
4874 max2y=min2y+maxy-miny;
4876 fprintf(fd,"set grid\n");
4877 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", metric?miny*METERS_PER_FOOT:miny, metric?maxy*METERS_PER_FOOT:maxy);
4878 fprintf(fd,"set y2range [%2.3f to %2.3f]\n", metric?min2y*METERS_PER_FOOT:min2y, metric?max2y*METERS_PER_FOOT:max2y);
4879 fprintf(fd,"set xrange [-0.5 to %2.3f]\n",metric?KM_PER_MILE*rint(distance+0.5):rint(distance+0.5));
4880 fprintf(fd,"set encoding iso_8859_1\n");
4881 fprintf(fd,"set term %s\n",term);
4884 fprintf(fd,"set title \"SPLAT! Path Profile Between %s and %s (%.2f%c azimuth)\\nWith First Fresnel Zone\"\n",destination.name, source.name, azimuth,176);
4887 fprintf(fd,"set title \"SPLAT! Height Profile Between %s and %s (%.2f%c azimuth)\"\n",destination.name, source.name, azimuth,176);
4890 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination));
4892 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination));
4897 fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (meters)\"\n",destination.name,source.name);
4900 fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (feet)\"\n",destination.name,source.name);
4907 fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between %s and %s (meters)\"\n",destination.name,source.name);
4910 fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between %s and %s (feet)\"\n",destination.name,source.name);
4913 fprintf(fd,"set output \"%s.%s\"\n",filename,ext);
4916 fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"reference.gp\" title \"Line of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines, \"fresnel.gp\" axes x1y1 title \"First Fresnel Zone (%.3f MHz)\" with lines, \"fresnel_pt_6.gp\" title \"%.0f%% of First Fresnel Zone\" with lines\n",f,fzone_clearance*100.0);
4919 fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines\n");
4923 x=system("gnuplot splat.gp");
4928 unlink("profile.gp");
4929 unlink("reference.gp");
4930 unlink("curvature.gp");
4934 unlink("fresnel.gp");
4935 unlink("fresnel_pt_6.gp");
4938 fprintf(stdout,"\nHeight plot written to: \"%s.%s\"",filename,ext);
4943 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
4946 void ObstructionAnalysis(struct site xmtr, struct site rcvr, double f, FILE *outfile)
4948 /* Perform an obstruction analysis along the
4949 path between receiver and transmitter. */
4953 double h_r, h_t, h_x, h_r_orig, cos_tx_angle, cos_test_angle,
4954 cos_tx_angle_f1, cos_tx_angle_fpt6, d_tx, d_x,
4955 h_r_f1, h_r_fpt6, h_f, h_los, lambda=0.0;
4956 char string[255], string_fpt6[255], string_f1[255];
4958 ReadPath(xmtr,rcvr);
4959 h_r=GetElevation(rcvr)+rcvr.alt+earthradius;
4963 h_t=GetElevation(xmtr)+xmtr.alt+earthradius;
4964 d_tx=5280.0*Distance(rcvr,xmtr);
4965 cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx);
4966 cos_tx_angle_f1=cos_tx_angle;
4967 cos_tx_angle_fpt6=cos_tx_angle;
4970 lambda=9.8425e8/(f*1e6);
4972 /* At each point along the path calculate the cosine
4973 of a sort of "inverse elevation angle" at the receiver.
4974 From the antenna, 0 deg. looks at the ground, and 90 deg.
4975 is parallel to the ground.
4977 Start at the receiver. If this is the lowest antenna,
4978 then terrain obstructions will be nearest to it. (Plus,
4979 that's the way SPLAT!'s original los() did it.)
4981 Calculate cosines only. That's sufficient to compare
4982 angles and it saves the extra computational burden of
4983 acos(). However, note the inverted comparison: if
4984 acos(A) > acos(B), then B > A. */
4986 for (x=path.length-1; x>0; x--)
4988 site_x.lat=path.lat[x];
4989 site_x.lon=path.lon[x];
4992 h_x=GetElevation(site_x)+earthradius;
4993 d_x=5280.0*Distance(rcvr,site_x);
4995 /* Deal with the LOS path first. */
4997 cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x);
4999 if (cos_tx_angle>cos_test_angle)
5002 fprintf(outfile,"Between %s and %s, SPLAT! detected obstructions at:\n\n",rcvr.name,xmtr.name);
5004 if (site_x.lat>=0.0)
5007 fprintf(outfile,"\t%.4f N, %.4f W, %5.2f kilometers, %6.2f meters AMSL\n",site_x.lat, site_x.lon, KM_PER_MILE*(d_x/5280.0), METERS_PER_FOOT*(h_x-earthradius));
5009 fprintf(outfile,"\t%.4f N, %.4f W, %5.2f miles, %6.2f feet AMSL\n",site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius);
5015 fprintf(outfile,"\t%.4f S, %.4f W, %5.2f kilometers, %6.2f meters AMSL\n",-site_x.lat, site_x.lon, KM_PER_MILE*(d_x/5280.0), METERS_PER_FOOT*(h_x-earthradius));
5018 fprintf(outfile,"\t%.4f S, %.4f W, %5.2f miles, %6.2f feet AMSL\n",-site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius);
5022 while (cos_tx_angle>cos_test_angle)
5025 cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x);
5026 cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx);
5031 /* Now clear the first Fresnel zone... */
5033 cos_tx_angle_f1=((h_r_f1*h_r_f1)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r_f1*d_tx);
5034 h_los=sqrt(h_r_f1*h_r_f1+d_x*d_x-2*h_r_f1*d_x*cos_tx_angle_f1);
5035 h_f=h_los-sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
5040 cos_tx_angle_f1=((h_r_f1*h_r_f1)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r_f1*d_tx);
5041 h_los=sqrt(h_r_f1*h_r_f1+d_x*d_x-2*h_r_f1*d_x*cos_tx_angle_f1);
5042 h_f=h_los-sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
5045 /* and clear the 60% F1 zone. */
5047 cos_tx_angle_fpt6=((h_r_fpt6*h_r_fpt6)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r_fpt6*d_tx);
5048 h_los=sqrt(h_r_fpt6*h_r_fpt6+d_x*d_x-2*h_r_fpt6*d_x*cos_tx_angle_fpt6);
5049 h_f=h_los-fzone_clearance*sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
5054 cos_tx_angle_fpt6=((h_r_fpt6*h_r_fpt6)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r_fpt6*d_tx);
5055 h_los=sqrt(h_r_fpt6*h_r_fpt6+d_x*d_x-2*h_r_fpt6*d_x*cos_tx_angle_fpt6);
5056 h_f=h_los-fzone_clearance*sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
5064 sprintf(string,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear all obstructions detected by SPLAT!\n",rcvr.name, METERS_PER_FOOT*(h_r-GetElevation(rcvr)-earthradius));
5066 sprintf(string,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear all obstructions detected by SPLAT!\n",rcvr.name, h_r-GetElevation(rcvr)-earthradius);
5070 sprintf(string,"\nNo obstructions to LOS path due to terrain were detected by SPLAT!\n");
5074 if (h_r_fpt6>h_r_orig)
5077 sprintf(string_fpt6,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear %.0f%c of the first Fresnel zone.\n",rcvr.name, METERS_PER_FOOT*(h_r_fpt6-GetElevation(rcvr)-earthradius),fzone_clearance*100.0,37);
5080 sprintf(string_fpt6,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear %.0f%c of the first Fresnel zone.\n",rcvr.name, h_r_fpt6-GetElevation(rcvr)-earthradius,fzone_clearance*100.0,37);
5084 sprintf(string_fpt6,"\n%.0f%c of the first Fresnel zone is clear.\n",fzone_clearance*100.0,37);
5086 if (h_r_f1>h_r_orig)
5089 sprintf(string_f1,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear the first Fresnel zone.\n",rcvr.name, METERS_PER_FOOT*(h_r_f1-GetElevation(rcvr)-earthradius));
5092 sprintf(string_f1,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear the first Fresnel zone.\n",rcvr.name, h_r_f1-GetElevation(rcvr)-earthradius);
5097 sprintf(string_f1,"\nThe first Fresnel zone is clear.\n\n");
5100 fprintf(outfile,"%s",string);
5104 fprintf(outfile,"%s",string_f1);
5105 fprintf(outfile,"%s",string_fpt6);
5109 void PathReport(struct site source, struct site destination, char *name, char graph_it)
5111 /* This function writes a SPLAT! Path Report (name.txt) to
5112 the filesystem. If (graph_it == 1), then gnuplot is invoked
5113 to generate an appropriate output file indicating the Longley-Rice
5114 model loss between the source and destination locations.
5115 "filename" is the name assigned to the output file generated
5116 by gnuplot. The filename extension is used to set gnuplot's
5117 terminal setting and output file type. If no extension is
5118 found, .png is assumed. */
5120 int x, y, z, errnum;
5121 char filename[255], term[30], ext[15], strmode[100],
5122 report_name[80], block=0;
5123 double maxloss=-100000.0, minloss=100000.0, loss, haavt,
5124 angle1, angle2, azimuth, pattern=1.0, patterndB=0.0,
5125 total_loss=0.0, cos_xmtr_angle, cos_test_angle=0.0,
5126 source_alt, test_alt, dest_alt, source_alt2, dest_alt2,
5127 distance, elevation, four_thirds_earth, field_strength,
5128 free_space_loss=0.0, voltage;
5129 FILE *fd=NULL, *fd2=NULL;
5131 sprintf(report_name,"%s-to-%s.txt",source.name,destination.name);
5133 four_thirds_earth=EARTHRADIUS*(4.0/3.0);
5135 for (x=0; report_name[x]!=0; x++)
5136 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
5139 fd2=fopen(report_name,"w");
5141 fprintf(fd2,"\n\t\t--==[ SPLAT! v%s Path Analysis ]==--\n\n",splat_version);
5142 fprintf(fd2,"-------------------------------------------------------------------------\n\n");
5143 fprintf(fd2,"Transmitter site: %s\n",source.name);
5145 if (source.lat>=0.0)
5147 fprintf(fd2,"Site location: %.4f North / %.4f West",source.lat, source.lon);
5148 fprintf(fd2, " (%s N / ", dec2dms(source.lat));
5154 fprintf(fd2,"Site location: %.4f South / %.4f West",-source.lat, source.lon);
5155 fprintf(fd2, " (%s S / ", dec2dms(source.lat));
5158 fprintf(fd2, "%s W)\n", dec2dms(source.lon));
5162 fprintf(fd2,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(source));
5163 fprintf(fd2,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*source.alt,METERS_PER_FOOT*(source.alt+GetElevation(source)));
5168 fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(source));
5169 fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",source.alt, source.alt+GetElevation(source));
5177 fprintf(fd2,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt);
5179 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
5182 azimuth=Azimuth(source,destination);
5183 angle1=ElevationAngle(source,destination);
5184 angle2=ElevationAngle2(source,destination,earthradius);
5186 if (got_azimuth_pattern || got_elevation_pattern)
5188 x=(int)rint(10.0*(10.0-angle2));
5190 if (x>=0 && x<=1000)
5191 pattern=(double)LR.antenna_pattern[(int)rint(azimuth)][x];
5193 patterndB=20.0*log10(pattern);
5197 fprintf(fd2,"Distance to %s: %.2f kilometers\n",destination.name,METERS_PER_FOOT*Distance(source,destination));
5200 fprintf(fd2,"Distance to %s: %.2f miles.\n",destination.name,Distance(source,destination));
5202 fprintf(fd2,"Azimuth to %s: %.2f degrees\n",destination.name,azimuth);
5205 fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",destination.name,angle1);
5208 fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",destination.name,angle1);
5210 if ((angle2-angle1)>0.0001)
5213 fprintf(fd2,"Depression");
5215 fprintf(fd2,"Elevation");
5217 fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2);
5220 fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
5224 fprintf(fd2,"Receiver site: %s\n",destination.name);
5226 if (destination.lat>=0.0)
5228 fprintf(fd2,"Site location: %.4f North / %.4f West",destination.lat, destination.lon);
5229 fprintf(fd2, " (%s N / ", dec2dms(destination.lat));
5234 fprintf(fd2,"Site location: %.4f South / %.4f West",-destination.lat, destination.lon);
5235 fprintf(fd2, " (%s S / ", dec2dms(destination.lat));
5238 fprintf(fd2, "%s W)\n", dec2dms(destination.lon));
5242 fprintf(fd2,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(destination));
5243 fprintf(fd2,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*destination.alt, METERS_PER_FOOT*(destination.alt+GetElevation(destination)));
5248 fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(destination));
5249 fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",destination.alt, destination.alt+GetElevation(destination));
5252 haavt=haat(destination);
5257 fprintf(fd2,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt);
5259 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
5263 fprintf(fd2,"Distance to %s: %.2f kilometers\n",source.name,KM_PER_MILE*Distance(source,destination));
5266 fprintf(fd2,"Distance to %s: %.2f miles\n",source.name,Distance(source,destination));
5268 azimuth=Azimuth(destination,source);
5270 angle1=ElevationAngle(destination,source);
5271 angle2=ElevationAngle2(destination,source,earthradius);
5273 fprintf(fd2,"Azimuth to %s: %.2f degrees.\n",source.name,azimuth);
5276 fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",source.name,angle1);
5279 fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",source.name,angle1);
5281 if ((angle2-angle1)>0.0001)
5284 fprintf(fd2,"Depression");
5286 fprintf(fd2,"Elevation");
5288 fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2);
5291 fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
5295 fprintf(fd2,"Longley-Rice path calculation parameters used in this analysis:\n\n");
5296 fprintf(fd2,"Earth's Dielectric Constant: %.3lf\n",LR.eps_dielect);
5297 fprintf(fd2,"Earth's Conductivity: %.3lf Siemens/meter\n",LR.sgm_conductivity);
5298 fprintf(fd2,"Atmospheric Bending Constant (N-units): %.3lf ppm\n",LR.eno_ns_surfref);
5299 fprintf(fd2,"Frequency: %.3lf MHz\n",LR.frq_mhz);
5300 fprintf(fd2,"Radio Climate: %d (",LR.radio_climate);
5302 switch (LR.radio_climate)
5305 fprintf(fd2,"Equatorial");
5309 fprintf(fd2,"Continental Subtropical");
5313 fprintf(fd2,"Maritime Subtropical");
5317 fprintf(fd2,"Desert");
5321 fprintf(fd2,"Continental Temperate");
5325 fprintf(fd2,"Martitime Temperate, Over Land");
5329 fprintf(fd2,"Maritime Temperate, Over Sea");
5333 fprintf(fd2,"Unknown");
5336 fprintf(fd2,")\nPolarization: %d (",LR.pol);
5339 fprintf(fd2,"Horizontal");
5342 fprintf(fd2,"Vertical");
5344 fprintf(fd2,")\nFraction of Situations: %.1lf%c\n",LR.conf*100.0,37);
5345 fprintf(fd2,"Fraction of Time: %.1lf%c\n",LR.rel*100.0,37);
5349 fprintf(fd2,"Transmitter ERP: ");
5352 fprintf(fd2,"%.1lf milliwatts\n",1000.0*LR.erp);
5354 if (LR.erp>=1.0 && LR.erp<10.0)
5355 fprintf(fd2,"%.1lf Watts\n",LR.erp);
5357 if (LR.erp>=10.0 && LR.erp<10.0e3)
5358 fprintf(fd2,"%.0lf Watts\n",LR.erp);
5361 fprintf(fd2,"%.3lf kilowatts\n",LR.erp/1.0e3);
5364 fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
5366 fprintf(fd2,"Summary for the link between %s and %s:\n\n",source.name, destination.name);
5369 fprintf(fd2,"%s antenna pattern towards %s: %.3f (%.2f dB)\n", source.name, destination.name, pattern, patterndB);
5371 ReadPath(source, destination); /* source=TX, destination=RX */
5373 /* Copy elevations along path into the elev_l[] array. */
5375 for (x=0; x<path.length; x++)
5376 elev_l[x+2]=path.elevation[x]*METERS_PER_FOOT;
5378 fd=fopen("profile.gp","w");
5380 azimuth=rint(Azimuth(source,destination));
5382 for (y=2; y<(path.length-1); y++) /* path.length-1 avoids LR error */
5384 distance=5280.0*path.distance[y];
5385 source_alt=four_thirds_earth+source.alt+path.elevation[0];
5386 dest_alt=four_thirds_earth+destination.alt+path.elevation[y];
5387 dest_alt2=dest_alt*dest_alt;
5388 source_alt2=source_alt*source_alt;
5390 /* Calculate the cosine of the elevation of
5391 the receiver as seen by the transmitter. */
5393 cos_xmtr_angle=((source_alt2)+(distance*distance)-(dest_alt2))/(2.0*source_alt*distance);
5395 if (got_elevation_pattern)
5397 /* If an antenna elevation pattern is available, the
5398 following code determines the elevation angle to
5399 the first obstruction along the path. */
5401 for (x=2, block=0; x<y && block==0; x++)
5403 distance=5280.0*(path.distance[y]-path.distance[x]);
5404 test_alt=four_thirds_earth+path.elevation[x];
5406 /* Calculate the cosine of the elevation
5407 angle of the terrain (test point)
5408 as seen by the transmitter. */
5410 cos_test_angle=((source_alt2)+(distance*distance)-(test_alt*test_alt))/(2.0*source_alt*distance);
5412 /* Compare these two angles to determine if
5413 an obstruction exists. Since we're comparing
5414 the cosines of these angles rather than
5415 the angles themselves, the sense of the
5416 following "if" statement is reversed from
5417 what it would be if the angles themselves
5420 if (cos_xmtr_angle>cos_test_angle)
5424 /* At this point, we have the elevation angle
5425 to the first obstruction (if it exists). */
5428 /* Determine path loss for each point along the
5429 path using Longley-Rice's point_to_point mode
5430 starting at x=2 (number_of_points = 1), the
5431 shortest distance terrain can play a role in
5434 elev_l[0]=y-1; /* (number of points - 1) */
5436 /* Distance between elevation samples */
5437 elev_l[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]);
5439 point_to_point(elev_l, source.alt*METERS_PER_FOOT,
5440 destination.alt*METERS_PER_FOOT, LR.eps_dielect,
5441 LR.sgm_conductivity, LR.eno_ns_surfref, LR.frq_mhz,
5442 LR.radio_climate, LR.pol, LR.conf, LR.rel, loss,
5446 elevation=((acos(cos_test_angle))/deg2rad)-90.0;
5448 elevation=((acos(cos_xmtr_angle))/deg2rad)-90.0;
5450 /* Integrate the antenna's radiation
5451 pattern into the overall path loss. */
5453 x=(int)rint(10.0*(10.0-elevation));
5455 if (x>=0 && x<=1000)
5457 pattern=(double)LR.antenna_pattern[(int)azimuth][x];
5460 patterndB=20.0*log10(pattern);
5466 total_loss=loss-patterndB;
5469 fprintf(fd,"%f\t%f\n",KM_PER_MILE*(path.distance[path.length-1]-path.distance[y]),total_loss);
5472 fprintf(fd,"%f\t%f\n",path.distance[path.length-1]-path.distance[y],total_loss);
5474 if (total_loss>maxloss)
5477 if (total_loss<minloss)
5483 distance=Distance(source,destination);
5488 free_space_loss=36.6+(20.0*log10(LR.frq_mhz))+(20.0*log10(distance));
5490 fprintf(fd2,"Free space path loss: %.2f dB\n",free_space_loss);
5493 fprintf(fd2,"Longley-Rice path loss: %.2f dB\n",loss);
5495 if (free_space_loss!=0.0)
5496 fprintf(fd2,"Attenuation due to effects of terrain: %.2f dB\n",loss-free_space_loss);
5499 fprintf(fd2,"Total path loss including %s antenna pattern: %.2f dB\n",source.name,total_loss);
5503 field_strength=(137.26+(20.0*log10(LR.frq_mhz))-total_loss)+(10.0*log10(LR.erp/1000.0));
5504 fprintf(fd2,"Field strength at %s: %.2f dBuV/meter\n", destination.name,field_strength);
5505 voltage=(pow(10.0,field_strength/20.0)*39.52726907)/LR.frq_mhz;
5506 fprintf(fd2,"Voltage produced by a terminated 50 ohm 0 dBd gain antenna: %.2f uV\n",voltage);
5507 voltage=(pow(10.0,field_strength/20.0)*48.41082007)/LR.frq_mhz;
5508 fprintf(fd2,"Voltage produced by a terminated 75 ohm 0 dBd gain antenna: %.2f uV\n",voltage);
5511 fprintf(fd2,"Mode of propagation: %s\n",strmode);
5513 fprintf(fd2,"\n-------------------------------------------------------------------------\n\n");
5516 fprintf(stdout,"\nPath Loss Report written to: \"%s\"\n",report_name);
5519 ObstructionAnalysis(source, destination, LR.frq_mhz, fd2);
5523 /* Skip plotting the graph if ONLY a path-loss report is needed. */
5529 /* Default filename and output file type */
5531 strncpy(filename,"loss\0",5);
5532 strncpy(term,"png\0",4);
5533 strncpy(ext,"png\0",4);
5538 /* Grab extension and terminal type from "name" */
5540 for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++)
5541 filename[x]=name[x];
5545 for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++)
5547 term[y]=tolower(name[x]);
5557 { /* No extension -- Default is png */
5560 strncpy(term,"png\0",4);
5561 strncpy(ext,"png\0",4);
5565 /* Either .ps or .postscript may be used
5566 as an extension for postscript output. */
5568 if (strncmp(term,"postscript",10)==0)
5569 strncpy(ext,"ps\0",3);
5571 else if (strncmp(ext,"ps",2)==0)
5572 strncpy(term,"postscript enhanced color\0",26);
5574 fd=fopen("splat.gp","w");
5576 fprintf(fd,"set grid\n");
5577 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minloss, maxloss);
5578 fprintf(fd,"set encoding iso_8859_1\n");
5579 fprintf(fd,"set term %s\n",term);
5580 fprintf(fd,"set title \"SPLAT! Loss Profile Along Path Between %s and %s (%.2f%c azimuth)\"\n",destination.name, source.name, Azimuth(destination,source),176);
5583 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(destination,source));
5585 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(destination,source));
5587 if (got_azimuth_pattern || got_elevation_pattern)
5588 fprintf(fd,"set ylabel \"Total Path Loss (including TX antenna pattern) (dB)");
5590 fprintf(fd,"set ylabel \"Longley-Rice Path Loss (dB)");
5592 fprintf(fd,"\"\nset output \"%s.%s\"\n",filename,ext);
5593 fprintf(fd,"plot \"profile.gp\" title \"Path Loss\" with lines\n");
5597 x=system("gnuplot splat.gp");
5602 unlink("profile.gp");
5603 unlink("reference.gp");
5605 fprintf(stdout,"Path loss plot written to: \"%s.%s\"\n",filename,ext);
5610 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
5614 unlink("profile.gp");
5617 void SiteReport(struct site xmtr)
5619 char report_name[80];
5624 sprintf(report_name,"%s-site_report.txt",xmtr.name);
5626 for (x=0; report_name[x]!=0; x++)
5627 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
5630 fd=fopen(report_name,"w");
5632 fprintf(fd,"\n\t--==[ SPLAT! v%s Site Analysis Report For: %s ]==--\n\n",splat_version,xmtr.name);
5634 fprintf(fd,"---------------------------------------------------------------------------\n\n");
5638 fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon);
5639 fprintf(fd, " (%s N / ",dec2dms(xmtr.lat));
5644 fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon);
5645 fprintf(fd, " (%s S / ",dec2dms(xmtr.lat));
5648 fprintf(fd, "%s W)\n",dec2dms(xmtr.lon));
5652 fprintf(fd,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(xmtr));
5653 fprintf(fd,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*xmtr.alt, METERS_PER_FOOT*(xmtr.alt+GetElevation(xmtr)));
5658 fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr));
5659 fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr));
5664 if (terrain>-4999.0)
5667 fprintf(fd,"Antenna height above average terrain: %.2f meters\n\n",METERS_PER_FOOT*terrain);
5669 fprintf(fd,"Antenna height above average terrain: %.2f feet\n\n",terrain);
5671 /* Display the average terrain between 2 and 10 miles
5672 from the transmitter site at azimuths of 0, 45, 90,
5673 135, 180, 225, 270, and 315 degrees. */
5675 for (azi=0; azi<=315; azi+=45)
5677 fprintf(fd,"Average terrain at %3d degrees azimuth: ",azi);
5678 terrain=AverageTerrain(xmtr,(double)azi,2.0,10.0);
5680 if (terrain>-4999.0)
5683 fprintf(fd,"%.2f meters AMSL\n",METERS_PER_FOOT*terrain);
5685 fprintf(fd,"%.2f feet AMSL\n",terrain);
5689 fprintf(fd,"No terrain\n");
5693 fprintf(fd,"\n---------------------------------------------------------------------------\n\n");
5695 fprintf(stdout,"\nSite analysis report written to: \"%s\"",report_name);
5698 void LoadTopoData(int max_lon, int min_lon, int max_lat, int min_lat)
5700 /* This function loads the SDF files required
5701 to cover the limits of the region specified. */
5703 int x, y, width, ymin, ymax;
5705 width=ReduceAngle(max_lon-min_lon);
5707 if ((max_lon-min_lon)<=180.0)
5709 for (y=0; y<=width; y++)
5710 for (x=min_lat; x<=max_lat; x++)
5712 ymin=(int)(min_lon+(double)y);
5728 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
5735 for (y=0; y<=width; y++)
5736 for (x=min_lat; x<=max_lat; x++)
5754 sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax);
5760 int LoadPLI(char *filename)
5762 /* This function reads a SPLAT! path-loss output
5763 file (-plo) for analysis and/or map generation. */
5765 int error=0, max_west, min_west, max_north, min_north;
5766 char string[80], *pointer=NULL;
5767 double latitude=0.0, longitude=0.0, azimuth=0.0, elevation=0.0,
5771 fd=fopen(filename,"r");
5775 fgets(string,78,fd);
5776 pointer=strchr(string,';');
5781 sscanf(string,"%d, %d",&max_west, &min_west);
5783 fgets(string,78,fd);
5784 pointer=strchr(string,';');
5789 sscanf(string,"%d, %d",&max_north, &min_north);
5791 fgets(string,78,fd);
5792 pointer=strchr(string,';');
5797 LoadTopoData(max_west-1, min_west, max_north-1, min_north);
5799 fprintf(stdout,"\nReading \"%s\"... ",filename);
5802 fgets(string,78,fd);
5803 sscanf(string,"%lf, %lf, %lf, %lf, %lf",&latitude, &longitude, &azimuth, &elevation, &loss);
5810 if (loss<=(double)maxdB)
5811 PutSignal(latitude,longitude,((unsigned char)round(loss)));
5813 fgets(string,78,fd);
5814 sscanf(string,"%lf, %lf, %lf, %lf, %lf",&latitude, &longitude, &azimuth, &elevation, &loss);
5819 fprintf(stdout," Done!\n");
5829 void WriteKML(struct site source, struct site destination)
5832 char block, report_name[80];
5833 double distance, rx_alt, tx_alt, cos_xmtr_angle,
5834 azimuth, cos_test_angle, test_alt;
5837 ReadPath(source,destination);
5839 sprintf(report_name,"%s-to-%s.kml",source.name,destination.name);
5841 for (x=0; report_name[x]!=0; x++)
5842 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
5845 fd=fopen(report_name,"w");
5847 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
5848 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.0\">\n");
5849 fprintf(fd,"<!-- Generated by SPLAT! Version %s -->\n",splat_version);
5850 fprintf(fd,"<Folder>\n");
5851 fprintf(fd,"<name>SPLAT! Path</name>\n");
5852 fprintf(fd,"<open>1</open>\n");
5853 fprintf(fd,"<description>Path Between %s and %s</description>\n",source.name,destination.name);
5855 fprintf(fd,"<Placemark>\n");
5856 fprintf(fd," <name>%s</name>\n",source.name);
5857 fprintf(fd," <description>\n");
5858 fprintf(fd," Transmit Site\n");
5860 if (source.lat>=0.0)
5861 fprintf(fd," <BR>%s North</BR>\n",dec2dms(source.lat));
5863 fprintf(fd," <BR>%s South</BR>\n",dec2dms(source.lat));
5865 fprintf(fd," <BR>%s West</BR>\n",dec2dms(source.lon));
5867 azimuth=Azimuth(source,destination);
5868 distance=Distance(source,destination);
5871 fprintf(fd," <BR>%.2f km",distance*KM_PER_MILE);
5873 fprintf(fd," <BR>%.2f miles",distance);
5875 fprintf(fd," to %s</BR>\n <BR>toward an azimuth of %.2f%c</BR>\n",destination.name,azimuth,176);
5877 fprintf(fd," </description>\n");
5878 fprintf(fd," <visibility>1</visibility>\n");
5879 fprintf(fd," <Style>\n");
5880 fprintf(fd," <IconStyle>\n");
5881 fprintf(fd," <Icon>\n");
5882 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
5883 fprintf(fd," <x>224</x>\n");
5884 fprintf(fd," <y>224</y>\n");
5885 fprintf(fd," <w>32</w>\n");
5886 fprintf(fd," <h>32</h>\n");
5887 fprintf(fd," </Icon>\n");
5888 fprintf(fd," </IconStyle>\n");
5889 fprintf(fd," </Style>\n");
5890 fprintf(fd," <Point>\n");
5891 fprintf(fd," <extrude>1</extrude>\n");
5892 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
5893 fprintf(fd," <coordinates>%f,%f,30</coordinates>\n",(source.lon<180.0?-source.lon:360.0-source.lon),source.lat);
5894 fprintf(fd," </Point>\n");
5895 fprintf(fd,"</Placemark>\n");
5897 fprintf(fd,"<Placemark>\n");
5898 fprintf(fd," <name>%s</name>\n",destination.name);
5899 fprintf(fd," <description>\n");
5900 fprintf(fd," Receive Site\n");
5902 if (destination.lat>=0.0)
5903 fprintf(fd," <BR>%s North</BR>\n",dec2dms(destination.lat));
5905 fprintf(fd," <BR>%s South</BR>\n",dec2dms(destination.lat));
5907 fprintf(fd," <BR>%s West</BR>\n",dec2dms(destination.lon));
5911 fprintf(fd," <BR>%.2f km",distance*KM_PER_MILE);
5913 fprintf(fd," <BR>%.2f miles",distance);
5915 fprintf(fd," to %s</BR>\n <BR>toward an azimuth of %.2f%c</BR>\n",source.name,Azimuth(destination,source),176);
5917 fprintf(fd," </description>\n");
5918 fprintf(fd," <visibility>1</visibility>\n");
5919 fprintf(fd," <Style>\n");
5920 fprintf(fd," <IconStyle>\n");
5921 fprintf(fd," <Icon>\n");
5922 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
5923 fprintf(fd," <x>224</x>\n");
5924 fprintf(fd," <y>224</y>\n");
5925 fprintf(fd," <w>32</w>\n");
5926 fprintf(fd," <h>32</h>\n");
5927 fprintf(fd," </Icon>\n");
5928 fprintf(fd," </IconStyle>\n");
5929 fprintf(fd," </Style>\n");
5930 fprintf(fd," <Point>\n");
5931 fprintf(fd," <extrude>1</extrude>\n");
5932 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
5933 fprintf(fd," <coordinates>%f,%f,30</coordinates>\n",(destination.lon<180.0?-destination.lon:360.0-destination.lon),destination.lat);
5934 fprintf(fd," </Point>\n");
5935 fprintf(fd,"</Placemark>\n");
5937 fprintf(fd,"<Placemark>\n");
5938 fprintf(fd,"<name>Point-to-Point Path</name>\n");
5939 fprintf(fd," <visibility>1</visibility>\n");
5940 fprintf(fd," <open>0</open>\n");
5941 fprintf(fd," <Style>\n");
5942 fprintf(fd," <LineStyle>\n");
5943 fprintf(fd," <color>7fffffff</color>\n");
5944 fprintf(fd," </LineStyle>\n");
5945 fprintf(fd," <PolyStyle>\n");
5946 fprintf(fd," <color>7fffffff</color>\n");
5947 fprintf(fd," </PolyStyle>\n");
5948 fprintf(fd," </Style>\n");
5949 fprintf(fd," <LineString>\n");
5950 fprintf(fd," <extrude>1</extrude>\n");
5951 fprintf(fd," <tessellate>1</tessellate>\n");
5952 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
5953 fprintf(fd," <coordinates>\n");
5955 for (x=0; x<path.length; x++)
5956 fprintf(fd," %f,%f,5\n",(path.lon[x]<180.0?-path.lon[x]:360.0-path.lon[x]),path.lat[x]);
5958 fprintf(fd," </coordinates>\n");
5959 fprintf(fd," </LineString>\n");
5960 fprintf(fd,"</Placemark>\n");
5962 fprintf(fd,"<Placemark>\n");
5963 fprintf(fd,"<name>Line-of-Sight Path</name>\n");
5964 fprintf(fd," <visibility>1</visibility>\n");
5965 fprintf(fd," <open>0</open>\n");
5966 fprintf(fd," <Style>\n");
5967 fprintf(fd," <LineStyle>\n");
5968 fprintf(fd," <color>ff00ff00</color>\n");
5969 fprintf(fd," </LineStyle>\n");
5970 fprintf(fd," <PolyStyle>\n");
5971 fprintf(fd," <color>7f00ff00</color>\n");
5972 fprintf(fd," </PolyStyle>\n");
5973 fprintf(fd," </Style>\n");
5974 fprintf(fd," <LineString>\n");
5975 fprintf(fd," <extrude>1</extrude>\n");
5976 fprintf(fd," <tessellate>1</tessellate>\n");
5977 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
5978 fprintf(fd," <coordinates>\n");
5980 /* Walk across the "path", indentifying obstructions along the way */
5982 for (y=0; y<path.length; y++)
5984 distance=5280.0*path.distance[y];
5985 tx_alt=earthradius+source.alt+path.elevation[0];
5986 rx_alt=earthradius+destination.alt+path.elevation[y];
5988 /* Calculate the cosine of the elevation of the
5989 transmitter as seen at the temp rx point. */
5991 cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
5993 for (x=y, block=0; x>=0 && block==0; x--)
5995 distance=5280.0*(path.distance[y]-path.distance[x]);
5996 test_alt=earthradius+path.elevation[x];
5998 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
6000 /* Compare these two angles to determine if
6001 an obstruction exists. Since we're comparing
6002 the cosines of these angles rather than
6003 the angles themselves, the following "if"
6004 statement is reversed from what it would
6005 be if the actual angles were compared. */
6007 if (cos_xmtr_angle>cos_test_angle)
6012 fprintf(fd," %f,%f,-30\n",(path.lon[y]<180.0?-path.lon[y]:360.0-path.lon[y]),path.lat[y]);
6014 fprintf(fd," %f,%f,5\n",(path.lon[y]<180.0?-path.lon[y]:360.0-path.lon[y]),path.lat[y]);
6017 fprintf(fd," </coordinates>\n");
6018 fprintf(fd," </LineString>\n");
6019 fprintf(fd,"</Placemark>\n");
6021 fprintf(fd," <LookAt>\n");
6022 fprintf(fd," <longitude>%f</longitude>\n",(source.lon<180.0?-source.lon:360.0-source.lon));
6023 fprintf(fd," <latitude>%f</latitude>\n",source.lat);
6024 fprintf(fd," <range>300.0</range>\n");
6025 fprintf(fd," <tilt>45.0</tilt>\n");
6026 fprintf(fd," <heading>%f</heading>\n",azimuth);
6027 fprintf(fd," </LookAt>\n");
6029 fprintf(fd,"</Folder>\n");
6030 fprintf(fd,"</kml>\n");
6034 fprintf(stdout, "\nKML file written to: \"%s\"",report_name);
6039 int main(int argc, char *argv[])
6041 int x, y, z=0, min_lat, min_lon, max_lat, max_lon,
6042 rxlat, rxlon, txlat, txlon, west_min, west_max,
6043 north_min, north_max;
6045 unsigned char coverage=0, LRmap=0, terrain_plot=0,
6046 elevation_plot=0, height_plot=0, map=0, nf=0,
6047 longley_plot=0, cities=0, bfs=0, txsites=0,
6048 norm=0, topomap=0, geo=0, kml=0, pt2pt_mode=0,
6049 area_mode=0, max_txsites, ngs=0, nolospath=0,
6052 char mapfile[255], header[80], city_file[5][255],
6053 elevation_file[255], height_file[255],
6054 longley_file[255], terrain_file[255],
6055 string[255], rxfile[255], *env=NULL,
6056 txfile[255], boundary_file[5][255],
6057 udt_file[255], rxsite=0, plo_filename[255],
6058 pli_filename[255], ext[20];
6060 double altitude=0.0, altitudeLR=0.0, tx_range=0.0,
6061 rx_range=0.0, deg_range=0.0, deg_limit,
6062 deg_range_lon, er_mult, freq=0.0;
6064 struct site tx_site[32], rx_site;
6071 fprintf(stdout,"\n\t\t --==[ SPLAT! v%s Available Options... ]==--\n\n",splat_version);
6072 fprintf(stdout," -t txsite(s).qth (max of 4 with -c, max of 30 with -L)\n");
6073 fprintf(stdout," -r rxsite.qth\n");
6074 fprintf(stdout," -c plot coverage of TX(s) with an RX antenna at X feet/meters AGL\n");
6075 fprintf(stdout," -L plot path loss map of TX based on an RX at X feet/meters AGL\n");
6076 fprintf(stdout," -s filename(s) of city/site file(s) to import (5 max)\n");
6077 fprintf(stdout," -b filename(s) of cartographic boundary file(s) to import (5 max)\n");
6078 fprintf(stdout," -p filename of terrain profile graph to plot\n");
6079 fprintf(stdout," -e filename of terrain elevation graph to plot\n");
6080 fprintf(stdout," -h filename of terrain height graph to plot\n");
6081 fprintf(stdout," -H filename of normalized terrain height graph to plot\n");
6082 fprintf(stdout," -l filename of Longley-Rice graph to plot\n");
6083 fprintf(stdout," -o filename of topographic map to generate (.ppm)\n");
6084 fprintf(stdout," -u filename of user-defined terrain file to import\n");
6085 fprintf(stdout," -d sdf file directory path (overrides path in ~/.splat_path file)\n");
6086 fprintf(stdout," -m earth radius multiplier\n");
6087 fprintf(stdout," -n do not plot LOS paths in .ppm maps\n");
6088 fprintf(stdout," -N do not produce unnecessary site or obstruction reports\n");
6089 fprintf(stdout," -f frequency for Fresnel zone calculation (MHz)\n");
6090 fprintf(stdout," -R modify default range for -c or -L (miles/kilometers)\n");
6091 fprintf(stdout," -db maximum loss contour to display on path loss maps (80-230 dB)\n");
6092 fprintf(stdout," -nf do not plot Fresnel zones in height plots\n");
6093 fprintf(stdout," -fz Fresnel zone clearance percentage (default = 60)\n");
6094 fprintf(stdout," -ngs display greyscale topography as white in .ppm files\n");
6095 fprintf(stdout," -erp override ERP in .lrp file (Watts)\n");
6096 fprintf(stdout," -pli filename of path-loss input file\n");
6097 fprintf(stdout," -plo filename of path-loss output file\n");
6098 fprintf(stdout," -udt filename of user defined terrain input file\n");
6099 fprintf(stdout," -kml generate Google Earth (.kml) compatible output\n");
6100 fprintf(stdout," -geo generate an Xastir .geo georeference file (with .ppm output)\n");
6101 fprintf(stdout," -metric employ metric rather than imperial units for all user I/O\n\n");
6102 fprintf(stdout,"If that flew by too fast, consider piping the output through 'less':\n");
6103 fprintf(stdout,"\n\tsplat | less\n\n");
6104 fprintf(stdout,"Type 'man splat', or see the documentation for more details.\n\n");
6119 elevation_file[0]=0;
6126 fzone_clearance=0.6;
6132 earthradius=EARTHRADIUS;
6134 sprintf(header,"\n\t\t--==[ Welcome To SPLAT! v%s ]==--\n\n", splat_version);
6138 tx_site[x].lat=91.0;
6139 tx_site[x].lon=361.0;
6142 for (x=0; x<MAXPAGES; x++)
6144 dem[x].min_el=32768;
6145 dem[x].max_el=-32768;
6146 dem[x].min_north=90;
6147 dem[x].max_north=-90;
6148 dem[x].min_west=360;
6152 /* Scan for command line arguments */
6154 for (x=1; x<=y; x++)
6156 if (strcmp(argv[x],"-R")==0)
6160 if (z<=y && argv[z][0] && argv[z][0]!='-')
6162 sscanf(argv[z],"%lf",&max_range);
6167 if (max_range>1000.0)
6172 if (strcmp(argv[x],"-m")==0)
6176 if (z<=y && argv[z][0] && argv[z][0]!='-')
6178 sscanf(argv[z],"%lf",&er_mult);
6186 earthradius*=er_mult;
6190 if (strcmp(argv[x],"-fz")==0)
6194 if (z<=y && argv[z][0] && argv[z][0]!='-')
6196 sscanf(argv[z],"%lf",&fzone_clearance);
6198 if (fzone_clearance<0.0 || fzone_clearance>100.0)
6199 fzone_clearance=60.0;
6201 fzone_clearance/=100.0;
6205 if (strcmp(argv[x],"-o")==0)
6209 if (z<=y && argv[z][0] && argv[z][0]!='-')
6210 strncpy(mapfile,argv[z],253);
6214 if (strcmp(argv[x],"-u")==0)
6218 if (z<=y && argv[z][0] && argv[z][0]!='-')
6219 strncpy(udt_file,argv[z],253);
6222 if (strcmp(argv[x],"-c")==0)
6226 if (z<=y && argv[z][0] && argv[z][0]!='-')
6228 sscanf(argv[z],"%lf",&altitude);
6236 if (strcmp(argv[x],"-db")==0 || strcmp(argv[x],"-dB")==0)
6240 if (z<=y && argv[z][0] && argv[z][0]!='-')
6242 sscanf(argv[z],"%d",&maxdB);
6254 if (strcmp(argv[x],"-p")==0)
6258 if (z<=y && argv[z][0] && argv[z][0]!='-')
6260 strncpy(terrain_file,argv[z],253);
6266 if (strcmp(argv[x],"-e")==0)
6270 if (z<=y && argv[z][0] && argv[z][0]!='-')
6272 strncpy(elevation_file,argv[z],253);
6278 if (strcmp(argv[x],"-h")==0 || strcmp(argv[x],"-H")==0)
6282 if (z<=y && argv[z][0] && argv[z][0]!='-')
6284 strncpy(height_file,argv[z],253);
6289 if (strcmp(argv[x],"-H")==0)
6295 if (strcmp(argv[x],"-metric")==0)
6298 if (strcmp(argv[x],"-geo")==0)
6301 if (strcmp(argv[x],"-kml")==0)
6304 if (strcmp(argv[x],"-nf")==0)
6307 if (strcmp(argv[x],"-ngs")==0)
6310 if (strcmp(argv[x],"-n")==0)
6313 if (strcmp(argv[x],"-N")==0)
6319 if (strcmp(argv[x],"-d")==0)
6323 if (z<=y && argv[z][0] && argv[z][0]!='-')
6324 strncpy(sdf_path,argv[z],253);
6327 if (strcmp(argv[x],"-t")==0)
6329 /* Read Transmitter Location */
6333 while (z<=y && argv[z][0] && argv[z][0]!='-' && txsites<30)
6335 strncpy(txfile,argv[z],253);
6336 tx_site[txsites]=LoadQTH(txfile);
6344 if (strcmp(argv[x],"-L")==0)
6348 if (z<=y && argv[z][0] && argv[z][0]!='-')
6350 sscanf(argv[z],"%lf",&altitudeLR);
6356 fprintf(stdout,"c and L are exclusive options, ignoring L.\n");
6360 if (strcmp(argv[x],"-l")==0)
6364 if (z<=y && argv[z][0] && argv[z][0]!='-')
6366 strncpy(longley_file,argv[z],253);
6372 if (strcmp(argv[x],"-r")==0)
6374 /* Read Receiver Location */
6378 if (z<=y && argv[z][0] && argv[z][0]!='-')
6380 strncpy(rxfile,argv[z],253);
6381 rx_site=LoadQTH(rxfile);
6387 if (strcmp(argv[x],"-s")==0)
6389 /* Read city file(s) */
6393 while (z<=y && argv[z][0] && argv[z][0]!='-' && cities<5)
6395 strncpy(city_file[cities],argv[z],253);
6403 if (strcmp(argv[x],"-b")==0)
6405 /* Read Boundary File(s) */
6409 while (z<=y && argv[z][0] && argv[z][0]!='-' && bfs<5)
6411 strncpy(boundary_file[bfs],argv[z],253);
6419 if (strcmp(argv[x],"-f")==0)
6423 if (z<=y && argv[z][0] && argv[z][0]!='-')
6425 sscanf(argv[z],"%lf",&freq);
6435 if (strcmp(argv[x],"-erp")==0)
6439 if (z<=y && argv[z][0] && argv[z][0]!='-')
6441 sscanf(argv[z],"%lf",&forced_erp);
6448 if (strcmp(argv[x],"-plo")==0)
6452 if (z<=y && argv[z][0] && argv[z][0]!='-')
6453 strncpy(plo_filename,argv[z],253);
6456 if (strcmp(argv[x],"-pli")==0)
6460 if (z<=y && argv[z][0] && argv[z][0]!='-')
6461 strncpy(pli_filename,argv[z],253);
6465 /* Perform some error checking on the arguments
6466 and switches parsed from the command-line.
6467 If an error is encountered, print a message
6468 and exit gracefully. */
6472 fprintf(stderr,"\n%c*** ERROR: No transmitter site(s) specified!\n\n",7);
6476 for (x=0, y=0; x<txsites; x++)
6478 if (tx_site[x].lat==91.0 && tx_site[x].lon==361.0)
6480 fprintf(stderr,"\n*** ERROR: Transmitter site #%d not found!",x+1);
6487 fprintf(stderr,"%c\n\n",7);
6491 if ((coverage+LRmap+pli_filename[0])==0 && rx_site.lat==91.0 && rx_site.lon==361.0)
6493 if (max_range!=0.0 && txsites!=0)
6495 /* Plot topographic map of radius "max_range" */
6503 fprintf(stderr,"\n%c*** ERROR: No receiver site found or specified!\n\n",7);
6508 /* No major errors were detected. Whew! :-) */
6510 /* Adjust input parameters if -metric option is used */
6514 altitudeLR/=METERS_PER_FOOT; /* meters --> feet */
6515 max_range/=KM_PER_MILE; /* kilometers --> miles */
6516 altitude/=METERS_PER_FOOT; /* meters --> feet */
6519 /* If no SDF path was specified on the command line (-d), check
6520 for a path specified in the $HOME/.splat_path file. If the
6521 file is not found, then sdf_path[] remains NULL, and the
6522 current working directory is assumed to contain the SDF
6528 sprintf(string,"%s/.splat_path",env);
6529 fd=fopen(string,"r");
6533 fgets(string,253,fd);
6535 /* Remove <CR> and/or <LF> from string */
6537 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0 && x<253; x++);
6540 strncpy(sdf_path,string,253);
6546 /* Ensure a trailing '/' is present in sdf_path */
6552 if (sdf_path[x-1]!='/' && x!=0)
6559 fprintf(stdout,"%s",header);
6562 if (pli_filename[0])
6564 y=LoadPLI(pli_filename);
6566 for (x=0; x<txsites && x<max_txsites; x++)
6567 PlaceMarker(tx_site[x]);
6570 PlaceMarker(rx_site);
6574 for (x=0; x<bfs; x++)
6575 LoadBoundaries(boundary_file[x]);
6580 for (x=0; x<cities; x++)
6581 LoadCities(city_file[x]);
6584 WritePPMLR(mapfile,geo,kml,ngs,tx_site,txsites);
6595 min_lon=(int)floor(tx_site[0].lon);
6596 max_lon=(int)floor(tx_site[0].lon);
6598 for (y=0, z=0; z<txsites && z<max_txsites; z++)
6600 txlat=(int)floor(tx_site[z].lat);
6601 txlon=(int)floor(tx_site[z].lon);
6609 if (LonDiff(txlon,min_lon)<0.0)
6612 if (LonDiff(txlon,max_lon)>0.0)
6618 rxlat=(int)floor(rx_site.lat);
6619 rxlon=(int)floor(rx_site.lon);
6627 if (LonDiff(rxlon,min_lon)<0.0)
6630 if (LonDiff(rxlon,max_lon)>0.0)
6635 /* Load the required SDF files */
6637 LoadTopoData(max_lon, min_lon, max_lat, min_lat);
6639 if (area_mode || topomap)
6641 for (z=0; z<txsites && z<max_txsites; z++)
6643 /* "Ball park" estimates used to load any additional
6644 SDF files required to conduct this analysis. */
6646 tx_range=sqrt(1.5*(tx_site[z].alt+GetElevation(tx_site[z])));
6649 rx_range=sqrt(1.5*altitudeLR);
6651 rx_range=sqrt(1.5*altitude);
6653 /* deg_range determines the maximum
6654 amount of topo data we read */
6656 deg_range=(tx_range+rx_range)/69.0;
6658 /* max_range sets the maximum size of the
6659 analysis. A small, non-zero amount can
6660 be used to shrink the size of the analysis
6661 and limit the amount of topo data read by
6662 SPLAT! A very large number will only increase
6663 the width of the analysis, not the size of
6667 max_range=tx_range+rx_range;
6669 if (max_range<(tx_range+rx_range))
6670 deg_range=max_range/69.0;
6672 /* Prevent the demand for a really wide coverage
6673 from allocating more "pages" than are available
6678 case 2: deg_limit=0.25;
6681 case 4: deg_limit=0.5;
6684 case 9: deg_limit=1.0;
6687 case 16: deg_limit=2.0;
6690 case 25: deg_limit=3.0;
6693 if (tx_site[z].lat<70.0)
6694 deg_range_lon=deg_range/cos(deg2rad*tx_site[z].lat);
6696 deg_range_lon=deg_range/cos(deg2rad*70.0);
6698 /* Correct for squares in degrees not being square in miles */
6700 if (deg_range>deg_limit)
6701 deg_range=deg_limit;
6703 if (deg_range_lon>deg_limit)
6704 deg_range_lon=deg_limit;
6706 north_min=(int)floor(tx_site[z].lat-deg_range);
6707 north_max=(int)floor(tx_site[z].lat+deg_range);
6709 west_min=(int)floor(tx_site[z].lon-deg_range_lon);
6714 while (west_min>=360)
6717 west_max=(int)floor(tx_site[z].lon+deg_range_lon);
6722 while (west_max>=360)
6725 if (north_min<min_lat)
6728 if (north_max>max_lat)
6731 if (LonDiff(west_min,min_lon)<0.0)
6734 if (LonDiff(west_max,max_lon)>0.0)
6738 /* Load any additional SDF files, if required */
6740 LoadTopoData(max_lon, min_lon, max_lat, min_lat);
6747 /***** Let the SPLATting begin! *****/
6751 PlaceMarker(rx_site);
6755 /* Grab extension to determine graphic file type */
6757 for (x=0; longley_file[x]!='.' && longley_file[x]!=0 && x<80; x++);
6759 if (longley_file[x]=='.')
6762 for (y=1, z=x, x++; longley_file[x]!=0 && x<253 && y<14; x++, y++)
6763 ext[y]=longley_file[x];
6771 ext[0]=0; /* No extension */
6778 for (x=0; terrain_file[x]!='.' && terrain_file[x]!=0 && x<80; x++);
6780 if (terrain_file[x]=='.') /* extension */
6783 for (y=1, z=x, x++; terrain_file[x]!=0 && x<253 && y<14; x++, y++)
6784 ext[y]=terrain_file[x];
6792 ext[0]=0; /* No extension */
6799 for (x=0; elevation_file[x]!='.' && elevation_file[x]!=0 && x<80; x++);
6801 if (elevation_file[x]=='.') /* extension */
6804 for (y=1, z=x, x++; elevation_file[x]!=0 && x<253 && y<14; x++, y++)
6805 ext[y]=elevation_file[x];
6808 elevation_file[z]=0;
6813 ext[0]=0; /* No extension */
6814 elevation_file[x]=0;
6820 for (x=0; height_file[x]!='.' && height_file[x]!=0 && x<80; x++);
6822 if (height_file[x]=='.') /* extension */
6825 for (y=1, z=x, x++; height_file[x]!=0 && x<253 && y<14; x++, y++)
6826 ext[y]=height_file[x];
6834 ext[0]=0; /* No extension */
6839 for (x=0; x<txsites && x<4; x++)
6841 PlaceMarker(tx_site[x]);
6848 PlotPath(tx_site[x],rx_site,1);
6852 PlotPath(tx_site[x],rx_site,8);
6856 PlotPath(tx_site[x],rx_site,16);
6860 PlotPath(tx_site[x],rx_site,32);
6864 if (nositereports==0)
6865 SiteReport(tx_site[x]);
6868 WriteKML(tx_site[x],rx_site);
6871 sprintf(string,"%s-%c%s%c",longley_file,'1'+x,ext,0);
6873 sprintf(string,"%s%s%c",longley_file,ext,0);
6875 if (nositereports==0)
6877 if (longley_file[0]==0)
6879 ReadLRParm(tx_site[x],0);
6880 PathReport(tx_site[x],rx_site,string,0); }
6884 ReadLRParm(tx_site[x],1);
6885 PathReport(tx_site[x],rx_site,string,longley_file[0]);
6892 sprintf(string,"%s-%c%s%c",terrain_file,'1'+x,ext,0);
6894 sprintf(string,"%s%s%c",terrain_file,ext,0);
6896 GraphTerrain(tx_site[x],rx_site,string);
6902 sprintf(string,"%s-%c%s%c",elevation_file,'1'+x,ext,0);
6904 sprintf(string,"%s%s%c",elevation_file,ext,0);
6906 GraphElevation(tx_site[x],rx_site,string);
6911 if (freq==0.0 && nf==0)
6915 sprintf(string,"%s-%c%s%c",height_file,'1'+x,ext,0);
6917 sprintf(string,"%s%s%c",height_file,ext,0);
6919 GraphHeight(tx_site[x],rx_site,string,freq,norm);
6924 if (area_mode && topomap==0)
6926 for (x=0; x<txsites && x<max_txsites; x++)
6929 PlotCoverage(tx_site[x],altitude);
6931 else if (ReadLRParm(tx_site[x],1))
6932 PlotLRMap(tx_site[x],altitudeLR,plo_filename);
6934 SiteReport(tx_site[x]);
6946 for (x=0; x<txsites && x<max_txsites; x++)
6947 PlaceMarker(tx_site[x]);
6950 for (y=0; y<cities; y++)
6951 LoadCities(city_file[y]);
6954 /* Load city and county boundary data files */
6958 for (y=0; y<bfs; y++)
6959 LoadBoundaries(boundary_file[y]);
6964 if (coverage || pt2pt_mode || topomap)
6965 WritePPM(mapfile,geo,kml,ngs);
6970 WritePPMLR(mapfile,geo,kml,ngs,tx_site,txsites);
6972 WritePPMSS(mapfile,geo,kml,ngs,tx_site,txsites);
6978 /* That's all, folks! */