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: 10-Apr-2009 *
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
40 #define ARRAYSIZE 4950
44 #define ARRAYSIZE 10870
48 #define ARRAYSIZE 19240
52 #define ARRAYSIZE 30025
56 #define ARRAYSIZE 43217
60 #define ARRAYSIZE 58813
64 #define ARRAYSIZE 76810
72 #define ARRAYSIZE 5092
76 #define ARRAYSIZE 14844
80 #define ARRAYSIZE 32600
84 #define ARRAYSIZE 57713
88 #define ARRAYSIZE 90072
92 #define ARRAYSIZE 129650
96 #define ARRAYSIZE 176437
100 #define ARRAYSIZE 230430
107 #define PI 3.141592653589793
111 #define TWOPI 6.283185307179586
115 #define HALFPI 1.570796326794896
118 #define DEG2RAD 1.74532925199e-02
119 #define EARTHRADIUS 20902230.97
120 #define METERS_PER_MILE 1609.344
121 #define METERS_PER_FOOT 0.3048
122 #define KM_PER_MILE 1.609344
123 #define FOUR_THIRDS 1.3333333333333
125 char string[255], sdf_path[255], opened=0, gpsav=0, splat_name[10],
126 splat_version[6], dashes[80];
128 double earthradius, max_range=0.0, forced_erp=-1.0, dpp, ppd,
129 fzone_clearance=0.6, forced_freq, clutter;
131 int min_north=90, max_north=-90, min_west=360, max_west=-1, ippd, mpi,
132 max_elevation=-32768, min_elevation=32768, bzerror, contour_threshold;
134 unsigned char got_elevation_pattern, got_azimuth_pattern, metric=0, dbm=0;
136 struct site { double lat;
143 struct path { double lat[ARRAYSIZE];
144 double lon[ARRAYSIZE];
145 double elevation[ARRAYSIZE];
146 double distance[ARRAYSIZE];
150 struct dem { int min_north;
156 short data[IPPD][IPPD];
157 unsigned char mask[IPPD][IPPD];
158 unsigned char signal[IPPD][IPPD];
161 struct LR { double eps_dielect;
162 double sgm_conductivity;
163 double eno_ns_surfref;
170 float antenna_pattern[361][1001];
173 struct region { unsigned char color[32][3];
178 double elev[ARRAYSIZE+10];
180 void point_to_point(double elev[], double tht_m, double rht_m,
181 double eps_dielect, double sgm_conductivity, double eno_ns_surfref,
182 double frq_mhz, int radio_climate, int pol, double conf,
183 double rel, double &dbloss, char *strmode, int &errnum);
185 double arccos(double x, double y)
187 /* This function implements the arc cosine function,
188 returning a value between 0 and TWOPI. */
201 int ReduceAngle(double angle)
203 /* This function normalizes the argument to
204 an integer angle between 0 and 180 degrees */
208 temp=acos(cos(angle*DEG2RAD));
210 return (int)rint(temp/DEG2RAD);
213 double LonDiff(double lon1, double lon2)
215 /* This function returns the short path longitudinal
216 difference between longitude1 and longitude2
217 as an angle between -180.0 and +180.0 degrees.
218 If lon1 is west of lon2, the result is positive.
219 If lon1 is east of lon2, the result is negative. */
234 char *dec2dms(double decimal)
236 /* Converts decimal degrees to degrees, minutes, seconds,
237 (DMS) and returns the result as a character string. */
240 int degrees, minutes, seconds;
268 snprintf(string,250,"%d%c %d\' %d\"", degrees*sign, 176, minutes, seconds);
272 int PutMask(double lat, double lon, int value)
274 /* Lines, text, markings, and coverage areas are stored in a
275 mask that is combined with topology data when topographic
276 maps are generated by SPLAT!. This function sets and resets
277 bits in the mask based on the latitude and longitude of the
283 for (indx=0, found=0; indx<MAXPAGES && found==0;)
285 x=(int)rint(ppd*(lat-dem[indx].min_north));
286 y=mpi-(int)rint(ppd*(LonDiff(dem[indx].max_west,lon)));
288 if (x>=0 && x<=mpi && y>=0 && y<=mpi)
296 dem[indx].mask[x][y]=value;
297 return ((int)dem[indx].mask[x][y]);
304 int OrMask(double lat, double lon, int value)
306 /* Lines, text, markings, and coverage areas are stored in a
307 mask that is combined with topology data when topographic
308 maps are generated by SPLAT!. This function sets bits in
309 the mask based on the latitude and longitude of the area
315 for (indx=0, found=0; indx<MAXPAGES && found==0;)
317 x=(int)rint(ppd*(lat-dem[indx].min_north));
318 y=mpi-(int)rint(ppd*(LonDiff(dem[indx].max_west,lon)));
320 if (x>=0 && x<=mpi && y>=0 && y<=mpi)
328 dem[indx].mask[x][y]|=value;
329 return ((int)dem[indx].mask[x][y]);
336 int GetMask(double lat, double lon)
338 /* This function returns the mask bits based on the latitude
339 and longitude given. */
341 return (OrMask(lat,lon,0));
344 int PutSignal(double lat, double lon, unsigned char signal)
346 /* This function writes a signal level (0-255)
347 at the specified location for later recall. */
352 for (indx=0, found=0; indx<MAXPAGES && found==0;)
354 x=(int)rint(ppd*(lat-dem[indx].min_north));
355 y=mpi-(int)rint(ppd*(LonDiff(dem[indx].max_west,lon)));
357 if (x>=0 && x<=mpi && y>=0 && y<=mpi)
365 dem[indx].signal[x][y]=signal;
366 return (dem[indx].signal[x][y]);
373 unsigned char GetSignal(double lat, double lon)
375 /* This function reads the signal level (0-255) at the
376 specified location that was previously written by the
377 complimentary PutSignal() function. */
382 for (indx=0, found=0; indx<MAXPAGES && found==0;)
384 x=(int)rint(ppd*(lat-dem[indx].min_north));
385 y=mpi-(int)rint(ppd*(LonDiff(dem[indx].max_west,lon)));
387 if (x>=0 && x<=mpi && y>=0 && y<=mpi)
394 return (dem[indx].signal[x][y]);
399 double GetElevation(struct site location)
401 /* This function returns the elevation (in feet) of any location
402 represented by the digital elevation model data in memory.
403 Function returns -5000.0 for locations not found in memory. */
409 for (indx=0, found=0; indx<MAXPAGES && found==0;)
411 x=(int)rint(ppd*(location.lat-dem[indx].min_north));
412 y=mpi-(int)rint(ppd*(LonDiff(dem[indx].max_west,location.lon)));
414 if (x>=0 && x<=mpi && y>=0 && y<=mpi)
421 elevation=3.28084*dem[indx].data[x][y];
428 int AddElevation(double lat, double lon, double height)
430 /* This function adds a user-defined terrain feature
431 (in meters AGL) to the digital elevation model data
432 in memory. Does nothing and returns 0 for locations
433 not found in memory. */
438 for (indx=0, found=0; indx<MAXPAGES && found==0;)
440 x=(int)rint(ppd*(lat-dem[indx].min_north));
441 y=mpi-(int)rint(ppd*(LonDiff(dem[indx].max_west,lon)));
443 if (x>=0 && x<=mpi && y>=0 && y<=mpi)
450 dem[indx].data[x][y]+=(short)rint(height);
455 double Distance(struct site site1, struct site site2)
457 /* This function returns the great circle distance
458 in miles between any two site locations. */
460 double lat1, lon1, lat2, lon2, distance;
462 lat1=site1.lat*DEG2RAD;
463 lon1=site1.lon*DEG2RAD;
464 lat2=site2.lat*DEG2RAD;
465 lon2=site2.lon*DEG2RAD;
467 distance=3959.0*acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos((lon1)-(lon2)));
472 double Azimuth(struct site source, struct site destination)
474 /* This function returns the azimuth (in degrees) to the
475 destination as seen from the location of the source. */
477 double dest_lat, dest_lon, src_lat, src_lon,
478 beta, azimuth, diff, num, den, fraction;
480 dest_lat=destination.lat*DEG2RAD;
481 dest_lon=destination.lon*DEG2RAD;
483 src_lat=source.lat*DEG2RAD;
484 src_lon=source.lon*DEG2RAD;
486 /* Calculate Surface Distance */
488 beta=acos(sin(src_lat)*sin(dest_lat)+cos(src_lat)*cos(dest_lat)*cos(src_lon-dest_lon));
490 /* Calculate Azimuth */
492 num=sin(dest_lat)-(sin(src_lat)*cos(beta));
493 den=cos(src_lat)*sin(beta);
496 /* Trap potential problems in acos() due to rounding */
504 /* Calculate azimuth */
506 azimuth=acos(fraction);
508 /* Reference it to True North */
510 diff=dest_lon-src_lon;
519 azimuth=TWOPI-azimuth;
521 return (azimuth/DEG2RAD);
524 double ElevationAngle(struct site source, struct site destination)
526 /* This function returns the angle of elevation (in degrees)
527 of the destination as seen from the source location.
528 A positive result represents an angle of elevation (uptilt),
529 while a negative result represents an angle of depression
530 (downtilt), as referenced to a normal to the center of
533 register double a, b, dx;
535 a=GetElevation(destination)+destination.alt+earthradius;
536 b=GetElevation(source)+source.alt+earthradius;
538 dx=5280.0*Distance(source,destination);
540 /* Apply the Law of Cosines */
542 return ((180.0*(acos(((b*b)+(dx*dx)-(a*a))/(2.0*b*dx)))/PI)-90.0);
545 void ReadPath(struct site source, struct site destination)
547 /* This function generates a sequence of latitude and
548 longitude positions between source and destination
549 locations along a great circle path, and stores
550 elevation and distance information for points
551 along that path in the "path" structure. */
554 double azimuth, distance, lat1, lon1, beta, den, num,
555 lat2, lon2, total_distance, dx, dy, path_length,
556 miles_per_sample, samples_per_radian=68755.0;
557 struct site tempsite;
559 lat1=source.lat*DEG2RAD;
560 lon1=source.lon*DEG2RAD;
562 lat2=destination.lat*DEG2RAD;
563 lon2=destination.lon*DEG2RAD;
566 samples_per_radian=68755.0;
569 samples_per_radian=206265.0;
571 azimuth=Azimuth(source,destination)*DEG2RAD;
573 total_distance=Distance(source,destination);
575 if (total_distance>(30.0/ppd)) /* > 0.5 pixel distance */
577 dx=samples_per_radian*acos(cos(lon1-lon2));
578 dy=samples_per_radian*acos(cos(lat1-lat2));
580 path_length=sqrt((dx*dx)+(dy*dy)); /* Total number of samples */
582 miles_per_sample=total_distance/path_length; /* Miles per sample */
591 miles_per_sample=0.0;
599 path.elevation[c]=GetElevation(source);
600 path.distance[c]=0.0;
603 for (distance=0.0, c=0; (total_distance!=0.0 && distance<=total_distance && c<ARRAYSIZE); c++, distance=miles_per_sample*(double)c)
605 beta=distance/3959.0;
606 lat2=asin(sin(lat1)*cos(beta)+cos(azimuth)*sin(beta)*cos(lat1));
607 num=cos(beta)-(sin(lat1)*sin(lat2));
608 den=cos(lat1)*cos(lat2);
610 if (azimuth==0.0 && (beta>HALFPI-lat1))
613 else if (azimuth==HALFPI && (beta>HALFPI+lat1))
616 else if (fabs(num/den)>1.0)
621 if ((PI-azimuth)>=0.0)
622 lon2=lon1-arccos(num,den);
624 lon2=lon1+arccos(num,den);
640 path.elevation[c]=GetElevation(tempsite);
641 path.distance[c]=distance;
644 /* Make sure exact destination point is recorded at path.length-1 */
648 path.lat[c]=destination.lat;
649 path.lon[c]=destination.lon;
650 path.elevation[c]=GetElevation(destination);
651 path.distance[c]=total_distance;
658 path.length=ARRAYSIZE-1;
661 double ElevationAngle2(struct site source, struct site destination, double er)
663 /* This function returns the angle of elevation (in degrees)
664 of the destination as seen from the source location, UNLESS
665 the path between the sites is obstructed, in which case, the
666 elevation angle to the first obstruction is returned instead.
667 "er" represents the earth radius. */
671 double source_alt, destination_alt, cos_xmtr_angle,
672 cos_test_angle, test_alt, elevation, distance,
673 source_alt2, first_obstruction_angle=0.0;
678 ReadPath(source,destination);
680 distance=5280.0*Distance(source,destination);
681 source_alt=er+source.alt+GetElevation(source);
682 destination_alt=er+destination.alt+GetElevation(destination);
683 source_alt2=source_alt*source_alt;
685 /* Calculate the cosine of the elevation angle of the
686 destination (receiver) as seen by the source (transmitter). */
688 cos_xmtr_angle=((source_alt2)+(distance*distance)-(destination_alt*destination_alt))/(2.0*source_alt*distance);
690 /* Test all points in between source and destination locations to
691 see if the angle to a topographic feature generates a higher
692 elevation angle than that produced by the destination. Begin
693 at the source since we're interested in identifying the FIRST
694 obstruction along the path between source and destination. */
696 for (x=2, block=0; x<path.length && block==0; x++)
698 distance=5280.0*path.distance[x];
700 test_alt=earthradius+(path.elevation[x]==0.0?path.elevation[x]:path.elevation[x]+clutter);
702 cos_test_angle=((source_alt2)+(distance*distance)-(test_alt*test_alt))/(2.0*source_alt*distance);
704 /* Compare these two angles to determine if
705 an obstruction exists. Since we're comparing
706 the cosines of these angles rather than
707 the angles themselves, the sense of the
708 following "if" statement is reversed from
709 what it would be if the angles themselves
712 if (cos_xmtr_angle>=cos_test_angle)
715 first_obstruction_angle=((acos(cos_test_angle))/DEG2RAD)-90.0;
720 elevation=first_obstruction_angle;
723 elevation=((acos(cos_xmtr_angle))/DEG2RAD)-90.0;
730 double AverageTerrain(struct site source, double azimuthx, double start_distance, double end_distance)
732 /* This function returns the average terrain calculated in
733 the direction of "azimuth" (degrees) between "start_distance"
734 and "end_distance" (miles) from the source location. If
735 the terrain is all water (non-critical error), -5000.0 is
736 returned. If not enough SDF data has been loaded into
737 memory to complete the survey (critical error), then
738 -9999.0 is returned. */
740 int c, samples, endpoint;
741 double beta, lat1, lon1, lat2, lon2, num, den, azimuth, terrain=0.0;
742 struct site destination;
744 lat1=source.lat*DEG2RAD;
745 lon1=source.lon*DEG2RAD;
747 /* Generate a path of elevations between the source
748 location and the remote location provided. */
750 beta=end_distance/3959.0;
752 azimuth=DEG2RAD*azimuthx;
754 lat2=asin(sin(lat1)*cos(beta)+cos(azimuth)*sin(beta)*cos(lat1));
755 num=cos(beta)-(sin(lat1)*sin(lat2));
756 den=cos(lat1)*cos(lat2);
758 if (azimuth==0.0 && (beta>HALFPI-lat1))
761 else if (azimuth==HALFPI && (beta>HALFPI+lat1))
764 else if (fabs(num/den)>1.0)
769 if ((PI-azimuth)>=0.0)
770 lon2=lon1-arccos(num,den);
772 lon2=lon1+arccos(num,den);
784 destination.lat=lat2;
785 destination.lon=lon2;
787 /* If SDF data is missing for the endpoint of
788 the radial, then the average terrain cannot
789 be accurately calculated. Return -9999.0 */
791 if (GetElevation(destination)<-4999.0)
795 ReadPath(source,destination);
797 endpoint=path.length;
799 /* Shrink the length of the radial if the
800 outermost portion is not over U.S. land. */
802 for (c=endpoint-1; c>=0 && path.elevation[c]==0.0; c--);
806 for (c=0, samples=0; c<endpoint; c++)
808 if (path.distance[c]>=start_distance)
810 terrain+=(path.elevation[c]==0.0?path.elevation[c]:path.elevation[c]+clutter);
816 terrain=-5000.0; /* No land */
818 terrain=(terrain/(double)samples);
824 double haat(struct site antenna)
826 /* This function returns the antenna's Height Above Average
827 Terrain (HAAT) based on FCC Part 73.313(d). If a critical
828 error occurs, such as a lack of SDF data to complete the
829 survey, -5000.0 is returned. */
833 double terrain, avg_terrain, haat, sum=0.0;
835 /* Calculate the average terrain between 2 and 10 miles
836 from the antenna site at azimuths of 0, 45, 90, 135,
837 180, 225, 270, and 315 degrees. */
839 for (c=0, azi=0; azi<=315 && error==0; azi+=45)
841 terrain=AverageTerrain(antenna, (double)azi, 2.0, 10.0);
843 if (terrain<-9998.0) /* SDF data is missing */
846 if (terrain>-4999.0) /* It's land, not water */
848 sum+=terrain; /* Sum of averages */
857 avg_terrain=(sum/(double)c);
858 haat=(antenna.alt+GetElevation(antenna))-avg_terrain;
863 void PlaceMarker(struct site location)
865 /* This function places text and marker data in the mask array
866 for illustration on topographic maps generated by SPLAT!.
867 By default, SPLAT! centers text information BELOW the marker,
868 but may move it above, to the left, or to the right of the
869 marker depending on how much room is available on the map,
870 or depending on whether the area is already occupied by
871 another marker or label. If no room or clear space is
872 available on the map to place the marker and its associated
873 text, then the marker and text are not written to the map. */
876 char ok2print, occupied;
877 double x, y, lat, lon, textx=0.0, texty=0.0, xmin, xmax,
878 ymin, ymax, p1, p3, p6, p8, p12, p16, p24, label_length;
880 xmin=(double)min_north;
881 xmax=(double)max_north;
882 ymin=(double)min_west;
883 ymax=(double)max_west;
887 if (lat<xmax && lat>=xmin && (LonDiff(lon,ymax)<=0.0) && (LonDiff(lon,ymin)>=dpp))
900 /* Is Marker Position Clear Of Text Or Other Markers? */
902 for (a=0, x=lat-p3; (x<=xmax && x>=xmin && a<7); x+=p1, a++)
903 for (b=0, y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=dpp) && b<7; y+=p1, b++)
904 occupied|=(GetMask(x,y)&2);
908 /* Determine Where Text Can Be Positioned */
910 /* label_length=length in pixels.
911 Each character is 8 pixels wide. */
913 label_length=p1*(double)(strlen(location.name)<<3);
915 if ((LonDiff(lon+label_length,ymax)<=0.0) && (LonDiff(lon-label_length,ymin)>=dpp))
917 /* Default: Centered Text */
919 texty=lon+label_length/2.0;
923 /* Position Text Below The Marker */
930 /* Is This Position Clear Of
931 Text Or Other Markers? */
933 for (a=0, occupied=0; a<16; a++)
935 for (b=0; b<(int)strlen(location.name); b++)
936 for (c=0; c<8; c++, y-=p1)
937 occupied|=(GetMask(x,y)&2);
951 /* Position Text Above The Marker */
958 /* Is This Position Clear Of
959 Text Or Other Markers? */
961 for (a=0, occupied=0; a<16; a++)
963 for (b=0; b<(int)strlen(location.name); b++)
964 for (c=0; c<8; c++, y-=p1)
965 occupied|=(GetMask(x,y)&2);
980 if (LonDiff(lon-label_length,ymin)>=dpp)
982 /* Position Text To The
983 Right Of The Marker */
991 /* Is This Position Clear Of
992 Text Or Other Markers? */
994 for (a=0, occupied=0; a<16; a++)
996 for (b=0; b<(int)strlen(location.name); b++)
997 for (c=0; c<8; c++, y-=p1)
998 occupied|=(GetMask(x,y)&2);
1012 /* Position Text To The
1013 Left Of The Marker */
1016 texty=lon+p8+(label_length);
1021 /* Is This Position Clear Of
1022 Text Or Other Markers? */
1024 for (a=0, occupied=0; a<16; a++)
1026 for (b=0; b<(int)strlen(location.name); b++)
1027 for (c=0; c<8; c++, y-=p1)
1028 occupied|=(GetMask(x,y)&2);
1041 /* textx and texty contain the latitude and longitude
1042 coordinates that describe the placement of the text
1052 for (a=0; a<16; a++)
1054 for (b=0; b<(int)strlen(location.name); b++)
1056 byte=fontdata[16*(location.name[b])+a];
1058 for (c=128; c>0; c=c>>1, y-=p1)
1067 /* Draw Square Marker Centered
1068 On Location Specified */
1070 for (a=0, x=lat-p3; (x<=xmax && x>=xmin && a<7); x+=p1, a++)
1071 for (b=0, y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=dpp) && b<7; y+=p1, b++)
1078 double ReadBearing(char *input)
1080 /* This function takes numeric input in the form of a character
1081 string, and returns an equivalent bearing in degrees as a
1082 decimal number (double). The input may either be expressed
1083 in decimal format (40.139722) or degree, minute, second
1084 format (40 08 23). This function also safely handles
1085 extra spaces found either leading, trailing, or
1086 embedded within the numbers expressed in the
1087 input string. Decimal seconds are permitted. */
1089 double seconds, bearing=0.0;
1091 int a, b, length, degrees, minutes;
1093 /* Copy "input" to "string", and ignore any extra
1094 spaces that might be present in the process. */
1097 length=strlen(input);
1099 for (a=0, b=0; a<length && a<18; a++)
1101 if ((input[a]!=32 && input[a]!='\n') || (input[a]==32 && input[a+1]!=32 && input[a+1]!='\n' && b!=0))
1110 /* Count number of spaces in the clean string. */
1112 length=strlen(string);
1114 for (a=0, b=0; a<length; a++)
1118 if (b==0) /* Decimal Format (40.139722) */
1119 sscanf(string,"%lf",&bearing);
1121 if (b==2) /* Degree, Minute, Second Format (40 08 23.xx) */
1123 sscanf(string,"%d %d %lf",°rees, &minutes, &seconds);
1125 bearing=fabs((double)degrees);
1126 bearing+=fabs(((double)minutes)/60.0);
1127 bearing+=fabs(seconds/3600.0);
1129 if ((degrees<0) || (minutes<0) || (seconds<0.0))
1133 /* Anything else returns a 0.0 */
1135 if (bearing>360.0 || bearing<-360.0)
1141 struct site LoadQTH(char *filename)
1143 /* This function reads SPLAT! .qth (site location) files.
1144 The latitude and longitude may be expressed either in
1145 decimal degrees, or in degree, minute, second format.
1146 Antenna height is assumed to be expressed in feet above
1147 ground level (AGL), unless followed by the letter 'M',
1148 or 'm', or by the word "meters" or "Meters", in which
1149 case meters is assumed, and is handled accordingly. */
1152 char string[50], qthfile[255], *s=NULL;
1153 struct site tempsite;
1157 strncpy(qthfile, filename, 254);
1159 if (qthfile[x-3]!='q' || qthfile[x-2]!='t' || qthfile[x-1]!='h')
1164 strncat(qthfile,".qth\0",5);
1171 tempsite.filename[0]=0;
1173 fd=fopen(qthfile,"r");
1178 s=fgets(string,49,fd);
1180 /* Strip <CR> and/or <LF> from end of site name */
1182 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; tempsite.name[x]=string[x], x++);
1187 s=fgets(string,49,fd);
1188 tempsite.lat=ReadBearing(string);
1190 /* Site Longitude */
1191 s=fgets(string,49,fd);
1192 tempsite.lon=ReadBearing(string);
1194 if (tempsite.lon<0.0)
1195 tempsite.lon+=360.0;
1197 /* Antenna Height */
1198 s=fgets(string,49,fd);
1201 /* Remove <CR> and/or <LF> from antenna height string */
1202 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0; x++);
1206 /* Antenna height may either be in feet or meters.
1207 If the letter 'M' or 'm' is discovered in
1208 the string, then this is an indication that
1209 the value given is expressed in meters, and
1210 must be converted to feet before exiting. */
1212 for (x=0; string[x]!='M' && string[x]!='m' && string[x]!=0 && x<48; x++);
1214 if (string[x]=='M' || string[x]=='m')
1217 sscanf(string,"%f",&tempsite.alt);
1218 tempsite.alt*=3.28084;
1224 sscanf(string,"%f",&tempsite.alt);
1227 for (x=0; x<254 && qthfile[x]!=0; x++)
1228 tempsite.filename[x]=qthfile[x];
1230 tempsite.filename[x]=0;
1236 void LoadPAT(char *filename)
1238 /* This function reads and processes antenna pattern (.az
1239 and .el) files that correspond in name to previously
1240 loaded SPLAT! .lrp files. */
1242 int a, b, w, x, y, z, last_index, next_index, span;
1243 char string[255], azfile[255], elfile[255], *pointer=NULL, *s=NULL;
1244 float az, xx, elevation, amplitude, rotation, valid1, valid2,
1245 delta, azimuth[361], azimuth_pattern[361], el_pattern[10001],
1246 elevation_pattern[361][1001], slant_angle[361], tilt,
1247 mechanical_tilt=0.0, tilt_azimuth, tilt_increment, sum;
1249 unsigned char read_count[10001];
1251 for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++)
1253 azfile[x]=filename[x];
1254 elfile[x]=filename[x];
1269 got_azimuth_pattern=0;
1270 got_elevation_pattern=0;
1272 /* Load .az antenna pattern file */
1274 fd=fopen(azfile,"r");
1278 /* Clear azimuth pattern array */
1280 for (x=0; x<=360; x++)
1287 /* Read azimuth pattern rotation
1288 in degrees measured clockwise
1291 s=fgets(string,254,fd);
1292 pointer=strchr(string,';');
1297 sscanf(string,"%f",&rotation);
1300 /* Read azimuth (degrees) and corresponding
1301 normalized field radiation pattern amplitude
1302 (0.0 to 1.0) until EOF is reached. */
1304 s=fgets(string,254,fd);
1305 pointer=strchr(string,';');
1310 sscanf(string,"%f %f",&az, &litude);
1316 if (x>=0 && x<=360 && fd!=NULL)
1318 azimuth[x]+=amplitude;
1322 s=fgets(string,254,fd);
1323 pointer=strchr(string,';');
1328 sscanf(string,"%f %f",&az, &litude);
1330 } while (feof(fd)==0);
1335 /* Handle 0=360 degree ambiguity */
1337 if ((read_count[0]==0) && (read_count[360]!=0))
1339 read_count[0]=read_count[360];
1340 azimuth[0]=azimuth[360];
1343 if ((read_count[0]!=0) && (read_count[360]==0))
1345 read_count[360]=read_count[0];
1346 azimuth[360]=azimuth[0];
1349 /* Average pattern values in case more than
1350 one was read for each degree of azimuth. */
1352 for (x=0; x<=360; x++)
1354 if (read_count[x]>1)
1355 azimuth[x]/=(float)read_count[x];
1358 /* Interpolate missing azimuths
1359 to completely fill the array */
1364 for (x=0; x<=360; x++)
1366 if (read_count[x]!=0)
1374 if (last_index!=-1 && next_index!=-1)
1376 valid1=azimuth[last_index];
1377 valid2=azimuth[next_index];
1379 span=next_index-last_index;
1380 delta=(valid2-valid1)/(float)span;
1382 for (y=last_index+1; y<next_index; y++)
1383 azimuth[y]=azimuth[y-1]+delta;
1390 /* Perform azimuth pattern rotation
1391 and load azimuth_pattern[361] with
1392 azimuth pattern data in its final form. */
1394 for (x=0; x<360; x++)
1396 y=x+(int)rintf(rotation);
1401 azimuth_pattern[y]=azimuth[x];
1404 azimuth_pattern[360]=azimuth_pattern[0];
1406 got_azimuth_pattern=255;
1409 /* Read and process .el file */
1411 fd=fopen(elfile,"r");
1415 for (x=0; x<=10000; x++)
1421 /* Read mechanical tilt (degrees) and
1422 tilt azimuth in degrees measured
1423 clockwise from true North. */
1425 s=fgets(string,254,fd);
1426 pointer=strchr(string,';');
1431 sscanf(string,"%f %f",&mechanical_tilt, &tilt_azimuth);
1433 /* Read elevation (degrees) and corresponding
1434 normalized field radiation pattern amplitude
1435 (0.0 to 1.0) until EOF is reached. */
1437 s=fgets(string,254,fd);
1438 pointer=strchr(string,';');
1443 sscanf(string,"%f %f", &elevation, &litude);
1447 /* Read in normalized radiated field values
1448 for every 0.01 degrees of elevation between
1449 -10.0 and +90.0 degrees */
1451 x=(int)rintf(100.0*(elevation+10.0));
1453 if (x>=0 && x<=10000)
1455 el_pattern[x]+=amplitude;
1459 s=fgets(string,254,fd);
1460 pointer=strchr(string,';');
1465 sscanf(string,"%f %f", &elevation, &litude);
1470 /* Average the field values in case more than
1471 one was read for each 0.01 degrees of elevation. */
1473 for (x=0; x<=10000; x++)
1475 if (read_count[x]>1)
1476 el_pattern[x]/=(float)read_count[x];
1479 /* Interpolate between missing elevations (if
1480 any) to completely fill the array and provide
1481 radiated field values for every 0.01 degrees of
1487 for (x=0; x<=10000; x++)
1489 if (read_count[x]!=0)
1497 if (last_index!=-1 && next_index!=-1)
1499 valid1=el_pattern[last_index];
1500 valid2=el_pattern[next_index];
1502 span=next_index-last_index;
1503 delta=(valid2-valid1)/(float)span;
1505 for (y=last_index+1; y<next_index; y++)
1506 el_pattern[y]=el_pattern[y-1]+delta;
1513 /* Fill slant_angle[] array with offset angles based
1514 on the antenna's mechanical beam tilt (if any)
1515 and tilt direction (azimuth). */
1517 if (mechanical_tilt==0.0)
1519 for (x=0; x<=360; x++)
1525 tilt_increment=mechanical_tilt/90.0;
1527 for (x=0; x<=360; x++)
1530 y=(int)rintf(tilt_azimuth+xx);
1539 slant_angle[y]=-(tilt_increment*(90.0-xx));
1542 slant_angle[y]=-(tilt_increment*(xx-270.0));
1546 slant_angle[360]=slant_angle[0]; /* 360 degree wrap-around */
1548 for (w=0; w<=360; w++)
1550 tilt=slant_angle[w];
1552 /** Convert tilt angle to
1553 an array index offset **/
1555 y=(int)rintf(100.0*tilt);
1557 /* Copy shifted el_pattern[10001] field
1558 values into elevation_pattern[361][1001]
1559 at the corresponding azimuth, downsampling
1560 (averaging) along the way in chunks of 10. */
1562 for (x=y, z=0; z<=1000; x+=10, z++)
1564 for (sum=0.0, a=0; a<10; a++)
1568 if (b>=0 && b<=10000)
1573 sum+=el_pattern[10000];
1576 elevation_pattern[w][z]=sum/10.0;
1580 got_elevation_pattern=255;
1583 for (x=0; x<=360; x++)
1585 for (y=0; y<=1000; y++)
1587 if (got_elevation_pattern)
1588 elevation=elevation_pattern[x][y];
1592 if (got_azimuth_pattern)
1593 az=azimuth_pattern[x];
1597 LR.antenna_pattern[x][y]=az*elevation;
1602 int LoadSDF_SDF(char *name)
1604 /* This function reads uncompressed SPLAT Data Files (.sdf)
1605 containing digital elevation model data into memory.
1606 Elevation data, maximum and minimum elevations, and
1607 quadrangle limits are stored in the first available
1610 int x, y, data, indx, minlat, minlon, maxlat, maxlon;
1611 char found, free_page=0, line[20], sdf_file[255],
1612 path_plus_name[255], *s=NULL;
1615 for (x=0; name[x]!='.' && name[x]!=0 && x<250; x++)
1616 sdf_file[x]=name[x];
1620 /* Parse filename for minimum latitude and longitude values */
1622 sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1630 /* Is it already in memory? */
1632 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
1634 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1638 /* Is room available to load it? */
1642 for (indx=0, free_page=0; indx<MAXPAGES && free_page==0; indx++)
1643 if (dem[indx].max_north==-90)
1649 if (free_page && found==0 && indx>=0 && indx<MAXPAGES)
1651 /* Search for SDF file in current working directory first */
1653 strncpy(path_plus_name,sdf_file,255);
1655 fd=fopen(path_plus_name,"rb");
1659 /* Next, try loading SDF file from path specified
1660 in $HOME/.splat_path file or by -d argument */
1662 strncpy(path_plus_name,sdf_path,255);
1663 strncat(path_plus_name,sdf_file,255);
1665 fd=fopen(path_plus_name,"rb");
1670 fprintf(stdout,"Loading \"%s\" into page %d...",path_plus_name,indx+1);
1673 s=fgets(line,19,fd);
1674 sscanf(line,"%d",&dem[indx].max_west);
1676 s=fgets(line,19,fd);
1677 sscanf(line,"%d",&dem[indx].min_north);
1679 s=fgets(line,19,fd);
1680 sscanf(line,"%d",&dem[indx].min_west);
1682 s=fgets(line,19,fd);
1683 sscanf(line,"%d",&dem[indx].max_north);
1685 for (x=0; x<ippd; x++)
1686 for (y=0; y<ippd; y++)
1688 s=fgets(line,19,fd);
1691 dem[indx].data[x][y]=data;
1692 dem[indx].signal[x][y]=0;
1693 dem[indx].mask[x][y]=0;
1695 if (data>dem[indx].max_el)
1696 dem[indx].max_el=data;
1698 if (data<dem[indx].min_el)
1699 dem[indx].min_el=data;
1704 if (dem[indx].min_el<min_elevation)
1705 min_elevation=dem[indx].min_el;
1707 if (dem[indx].max_el>max_elevation)
1708 max_elevation=dem[indx].max_el;
1711 max_north=dem[indx].max_north;
1713 else if (dem[indx].max_north>max_north)
1714 max_north=dem[indx].max_north;
1717 min_north=dem[indx].min_north;
1719 else if (dem[indx].min_north<min_north)
1720 min_north=dem[indx].min_north;
1723 max_west=dem[indx].max_west;
1727 if (abs(dem[indx].max_west-max_west)<180)
1729 if (dem[indx].max_west>max_west)
1730 max_west=dem[indx].max_west;
1735 if (dem[indx].max_west<max_west)
1736 max_west=dem[indx].max_west;
1741 min_west=dem[indx].min_west;
1745 if (fabs(dem[indx].min_west-min_west)<180.0)
1747 if (dem[indx].min_west<min_west)
1748 min_west=dem[indx].min_west;
1753 if (dem[indx].min_west>min_west)
1754 min_west=dem[indx].min_west;
1758 fprintf(stdout," Done!\n");
1772 char *BZfgets(BZFILE *bzfd, unsigned length)
1774 /* This function returns at most one less than 'length' number
1775 of characters from a bz2 compressed file whose file descriptor
1776 is pointed to by *bzfd. In operation, a buffer is filled with
1777 uncompressed data (size = BZBUFFER), which is then parsed
1778 and doled out as NULL terminated character strings every time
1779 this function is invoked. A NULL string indicates an EOF
1780 or error condition. */
1782 static int x, y, nBuf;
1783 static char buffer[BZBUFFER+1], output[BZBUFFER+1];
1786 if (opened!=1 && bzerror==BZ_OK)
1788 /* First time through. Initialize everything! */
1799 if (x==nBuf && bzerror!=BZ_STREAM_END && bzerror==BZ_OK && opened)
1801 /* Uncompress data into a static buffer */
1803 nBuf=BZ2_bzRead(&bzerror, bzfd, buffer, BZBUFFER);
1808 /* Build a string from buffer contents */
1810 output[y]=buffer[x];
1812 if (output[y]=='\n' || output[y]==0 || y==(int)length-1)
1831 int LoadSDF_BZ(char *name)
1833 /* This function reads .bz2 compressed SPLAT Data Files containing
1834 digital elevation model data into memory. Elevation data,
1835 maximum and minimum elevations, and quadrangle limits are
1836 stored in the first available dem[] structure. */
1838 int x, y, data, indx, minlat, minlon, maxlat, maxlon;
1839 char found, free_page=0, sdf_file[255], path_plus_name[255], *string;
1843 for (x=0; name[x]!='.' && name[x]!=0 && x<247; x++)
1844 sdf_file[x]=name[x];
1848 /* Parse sdf_file name for minimum latitude and longitude values */
1850 sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
1862 /* Is it already in memory? */
1864 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
1866 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
1870 /* Is room available to load it? */
1874 for (indx=0, free_page=0; indx<MAXPAGES && free_page==0; indx++)
1875 if (dem[indx].max_north==-90)
1881 if (free_page && found==0 && indx>=0 && indx<MAXPAGES)
1883 /* Search for SDF file in current working directory first */
1885 strncpy(path_plus_name,sdf_file,255);
1887 fd=fopen(path_plus_name,"rb");
1888 bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1890 if (fd==NULL || bzerror!=BZ_OK)
1892 /* Next, try loading SDF file from path specified
1893 in $HOME/.splat_path file or by -d argument */
1895 strncpy(path_plus_name,sdf_path,255);
1896 strncat(path_plus_name,sdf_file,255);
1898 fd=fopen(path_plus_name,"rb");
1899 bzfd=BZ2_bzReadOpen(&bzerror,fd,0,0,NULL,0);
1902 if (fd!=NULL && bzerror==BZ_OK)
1904 fprintf(stdout,"Loading \"%s\" into page %d...",path_plus_name,indx+1);
1907 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].max_west);
1908 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].min_north);
1909 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].min_west);
1910 sscanf(BZfgets(bzfd,255),"%d",&dem[indx].max_north);
1912 for (x=0; x<ippd; x++)
1913 for (y=0; y<ippd; y++)
1915 string=BZfgets(bzfd,20);
1918 dem[indx].data[x][y]=data;
1919 dem[indx].signal[x][y]=0;
1920 dem[indx].mask[x][y]=0;
1922 if (data>dem[indx].max_el)
1923 dem[indx].max_el=data;
1925 if (data<dem[indx].min_el)
1926 dem[indx].min_el=data;
1931 BZ2_bzReadClose(&bzerror,bzfd);
1933 if (dem[indx].min_el<min_elevation)
1934 min_elevation=dem[indx].min_el;
1936 if (dem[indx].max_el>max_elevation)
1937 max_elevation=dem[indx].max_el;
1940 max_north=dem[indx].max_north;
1942 else if (dem[indx].max_north>max_north)
1943 max_north=dem[indx].max_north;
1946 min_north=dem[indx].min_north;
1948 else if (dem[indx].min_north<min_north)
1949 min_north=dem[indx].min_north;
1952 max_west=dem[indx].max_west;
1956 if (abs(dem[indx].max_west-max_west)<180)
1958 if (dem[indx].max_west>max_west)
1959 max_west=dem[indx].max_west;
1964 if (dem[indx].max_west<max_west)
1965 max_west=dem[indx].max_west;
1970 min_west=dem[indx].min_west;
1974 if (abs(dem[indx].min_west-min_west)<180)
1976 if (dem[indx].min_west<min_west)
1977 min_west=dem[indx].min_west;
1982 if (dem[indx].min_west>min_west)
1983 min_west=dem[indx].min_west;
1987 fprintf(stdout," Done!\n");
2001 char LoadSDF(char *name)
2003 /* This function loads the requested SDF file from the filesystem.
2004 It first tries to invoke the LoadSDF_SDF() function to load an
2005 uncompressed SDF file (since uncompressed files load slightly
2006 faster). If that attempt fails, then it tries to load a
2007 compressed SDF file by invoking the LoadSDF_BZ() function.
2008 If that fails, then we can assume that no elevation data
2009 exists for the region requested, and that the region
2010 requested must be entirely over water. */
2012 int x, y, indx, minlat, minlon, maxlat, maxlon;
2013 char found, free_page=0;
2014 int return_value=-1;
2016 /* Try to load an uncompressed SDF first. */
2018 return_value=LoadSDF_SDF(name);
2020 /* If that fails, try loading a compressed SDF. */
2022 if (return_value==0 || return_value==-1)
2023 return_value=LoadSDF_BZ(name);
2025 /* If neither format can be found, then assume the area is water. */
2027 if (return_value==0 || return_value==-1)
2029 /* Parse SDF name for minimum latitude and longitude values */
2031 sscanf(name,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon);
2033 /* Is it already in memory? */
2035 for (indx=0, found=0; indx<MAXPAGES && found==0; indx++)
2037 if (minlat==dem[indx].min_north && minlon==dem[indx].min_west && maxlat==dem[indx].max_north && maxlon==dem[indx].max_west)
2041 /* Is room available to load it? */
2045 for (indx=0, free_page=0; indx<MAXPAGES && free_page==0; indx++)
2046 if (dem[indx].max_north==-90)
2052 if (free_page && found==0 && indx>=0 && indx<MAXPAGES)
2054 fprintf(stdout,"Region \"%s\" assumed as sea-level into page %d...",name,indx+1);
2057 dem[indx].max_west=maxlon;
2058 dem[indx].min_north=minlat;
2059 dem[indx].min_west=minlon;
2060 dem[indx].max_north=maxlat;
2062 /* Fill DEM with sea-level topography */
2064 for (x=0; x<ippd; x++)
2065 for (y=0; y<ippd; y++)
2067 dem[indx].data[x][y]=0;
2068 dem[indx].signal[x][y]=0;
2069 dem[indx].mask[x][y]=0;
2071 if (dem[indx].min_el>0)
2075 if (dem[indx].min_el<min_elevation)
2076 min_elevation=dem[indx].min_el;
2078 if (dem[indx].max_el>max_elevation)
2079 max_elevation=dem[indx].max_el;
2082 max_north=dem[indx].max_north;
2084 else if (dem[indx].max_north>max_north)
2085 max_north=dem[indx].max_north;
2088 min_north=dem[indx].min_north;
2090 else if (dem[indx].min_north<min_north)
2091 min_north=dem[indx].min_north;
2094 max_west=dem[indx].max_west;
2098 if (abs(dem[indx].max_west-max_west)<180)
2100 if (dem[indx].max_west>max_west)
2101 max_west=dem[indx].max_west;
2106 if (dem[indx].max_west<max_west)
2107 max_west=dem[indx].max_west;
2112 min_west=dem[indx].min_west;
2116 if (abs(dem[indx].min_west-min_west)<180)
2118 if (dem[indx].min_west<min_west)
2119 min_west=dem[indx].min_west;
2124 if (dem[indx].min_west>min_west)
2125 min_west=dem[indx].min_west;
2129 fprintf(stdout," Done!\n");
2136 return return_value;
2139 void LoadCities(char *filename)
2141 /* This function reads SPLAT! city/site files, and plots
2142 the locations and names of the cities and site locations
2143 read on topographic maps generated by SPLAT! */
2146 char input[80], str[3][80], *s=NULL;
2147 struct site city_site;
2150 fd=fopen(filename,"r");
2154 s=fgets(input,78,fd);
2156 fprintf(stdout,"\nReading \"%s\"... ",filename);
2159 while (fd!=NULL && feof(fd)==0)
2161 /* Parse line for name, latitude, and longitude */
2163 for (x=0, y=0, z=0; x<78 && input[x]!=0 && z<3; x++)
2165 if (input[x]!=',' && y<78)
2179 strncpy(city_site.name,str[0],49);
2180 city_site.lat=ReadBearing(str[1]);
2181 city_site.lon=ReadBearing(str[2]);
2184 if (city_site.lon<0.0)
2185 city_site.lon+=360.0;
2187 PlaceMarker(city_site);
2189 s=fgets(input,78,fd);
2193 fprintf(stdout,"Done!");
2198 fprintf(stderr,"\n*** ERROR: \"%s\": not found!",filename);
2201 void LoadUDT(char *filename)
2203 /* This function reads a file containing User-Defined Terrain
2204 features for their addition to the digital elevation model
2205 data used by SPLAT!. Elevations in the UDT file are evaluated
2206 and then copied into a temporary file under /tmp. Then the
2207 contents of the temp file are scanned, and if found to be unique,
2208 are added to the ground elevations described by the digital
2209 elevation data already loaded into memory. */
2211 int i, x, y, z, ypix, xpix, tempxpix, tempypix, fd=0, n=0;
2212 char input[80], str[3][80], tempname[15], *pointer=NULL, *s=NULL;
2213 double latitude, longitude, height, tempheight;
2214 FILE *fd1=NULL, *fd2=NULL;
2216 strcpy(tempname,"/tmp/XXXXXX\0");
2218 fd1=fopen(filename,"r");
2222 fd=mkstemp(tempname);
2223 fd2=fopen(tempname,"w");
2225 s=fgets(input,78,fd1);
2227 pointer=strchr(input,';');
2232 fprintf(stdout,"\nReading \"%s\"... ",filename);
2235 while (feof(fd1)==0)
2237 /* Parse line for latitude, longitude, height */
2239 for (x=0, y=0, z=0; x<78 && input[x]!=0 && z<3; x++)
2241 if (input[x]!=',' && y<78)
2255 latitude=ReadBearing(str[0]);
2256 longitude=ReadBearing(str[1]);
2261 /* Remove <CR> and/or <LF> from antenna height string */
2263 for (i=0; str[2][i]!=13 && str[2][i]!=10 && str[2][i]!=0; i++);
2267 /* The terrain feature may be expressed in either
2268 feet or meters. If the letter 'M' or 'm' is
2269 discovered in the string, then this is an
2270 indication that the value given is expressed
2271 in meters. Otherwise the height is interpreted
2272 as being expressed in feet. */
2274 for (i=0; str[2][i]!='M' && str[2][i]!='m' && str[2][i]!=0 && i<48; i++);
2276 if (str[2][i]=='M' || str[2][i]=='m')
2279 height=rint(atof(str[2]));
2285 height=rint(METERS_PER_FOOT*atof(str[2]));
2289 fprintf(fd2,"%d, %d, %f\n",(int)rint(latitude/dpp), (int)rint(longitude/dpp), height);
2291 s=fgets(input,78,fd1);
2293 pointer=strchr(input,';');
2303 fprintf(stdout,"Done!");
2306 fd1=fopen(tempname,"r");
2307 fd2=fopen(tempname,"r");
2311 n=fscanf(fd1,"%d, %d, %lf", &xpix, &ypix, &height);
2318 n=fscanf(fd2,"%d, %d, %lf", &tempxpix, &tempypix, &tempheight);
2322 if (x>y && xpix==tempxpix && ypix==tempypix)
2326 if (tempheight>height)
2332 n=fscanf(fd2,"%d, %d, %lf", &tempxpix, &tempypix, &tempheight);
2336 } while (feof(fd2)==0 && z==0);
2338 if (z==0) /* No duplicate found */
2339 AddElevation(xpix*dpp, ypix*dpp, height);
2341 n=fscanf(fd1,"%d, %d, %lf", &xpix, &ypix, &height);
2346 } while (feof(fd1)==0);
2354 fprintf(stderr,"\n*** ERROR: \"%s\": not found!",filename);
2356 fprintf(stdout,"\n");
2359 void LoadBoundaries(char *filename)
2361 /* This function reads Cartographic Boundary Files available from
2362 the U.S. Census Bureau, and plots the data contained in those
2363 files on the PPM Map generated by SPLAT!. Such files contain
2364 the coordinates that describe the boundaries of cities,
2365 counties, and states. */
2368 double lat0, lon0, lat1, lon1;
2369 char string[80], *s=NULL;
2370 struct site source, destination;
2373 fd=fopen(filename,"r");
2377 s=fgets(string,78,fd);
2379 fprintf(stdout,"\nReading \"%s\"... ",filename);
2384 s=fgets(string,78,fd);
2385 sscanf(string,"%lf %lf", &lon0, &lat0);
2386 s=fgets(string,78,fd);
2390 sscanf(string,"%lf %lf", &lon1, &lat1);
2393 source.lon=(lon0>0.0 ? 360.0-lon0 : -lon0);
2394 destination.lat=lat1;
2395 destination.lon=(lon1>0.0 ? 360.0-lon1 : -lon1);
2397 ReadPath(source,destination);
2399 for (x=0; x<path.length; x++)
2400 OrMask(path.lat[x],path.lon[x],4);
2405 s=fgets(string,78,fd);
2407 } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
2409 s=fgets(string,78,fd);
2411 } while (strncmp(string,"END",3)!=0 && feof(fd)==0);
2415 fprintf(stdout,"Done!");
2420 fprintf(stderr,"\n*** ERROR: \"%s\": not found!",filename);
2423 char ReadLRParm(struct site txsite, char forced_read)
2425 /* This function reads Longley-Rice parameter data for the
2426 transmitter site. The file name is the same as the txsite,
2427 except the filename extension is .lrp. If the needed file
2428 is not found, then the file "splat.lrp" is read from the
2429 current working directory. Failure to load this file under
2430 a forced_read condition will result in the default parameters
2431 hard coded into this function to be used and written to
2435 char filename[255], string[80], *pointer=NULL, *s=NULL, return_value=0;
2437 FILE *fd=NULL, *outfile=NULL;
2439 /* Default parameters */
2442 LR.sgm_conductivity=0.0;
2443 LR.eno_ns_surfref=0.0;
2451 /* Generate .lrp filename from txsite filename. */
2453 for (x=0; txsite.filename[x]!='.' && txsite.filename[x]!=0 && x<250; x++)
2454 filename[x]=txsite.filename[x];
2462 fd=fopen(filename,"r");
2466 /* Load default "splat.lrp" file */
2468 strncpy(filename,"splat.lrp\0",10);
2469 fd=fopen(filename,"r");
2474 s=fgets(string,80,fd);
2476 pointer=strchr(string,';');
2481 ok=sscanf(string,"%lf", &din);
2487 s=fgets(string,80,fd);
2489 pointer=strchr(string,';');
2494 ok=sscanf(string,"%lf", &din);
2499 LR.sgm_conductivity=din;
2501 s=fgets(string,80,fd);
2503 pointer=strchr(string,';');
2508 ok=sscanf(string,"%lf", &din);
2513 LR.eno_ns_surfref=din;
2515 s=fgets(string,80,fd);
2517 pointer=strchr(string,';');
2522 ok=sscanf(string,"%lf", &din);
2529 s=fgets(string,80,fd);
2531 pointer=strchr(string,';');
2536 ok=sscanf(string,"%d", &iin);
2541 LR.radio_climate=iin;
2543 s=fgets(string,80,fd);
2545 pointer=strchr(string,';');
2550 ok=sscanf(string,"%d", &iin);
2557 s=fgets(string,80,fd);
2559 pointer=strchr(string,';');
2564 ok=sscanf(string,"%lf", &din);
2571 s=fgets(string,80,fd);
2573 pointer=strchr(string,';');
2578 ok=sscanf(string,"%lf", &din);
2587 if (fgets(string,80,fd)!=NULL)
2589 pointer=strchr(string,':');
2594 if (sscanf(string,"%lf", &din))
2597 /* ERP in SPLAT! is referenced to 1 Watt
2598 into a dipole (0 dBd). If ERP is
2599 expressed in dBm (referenced to a
2600 0 dBi radiator), convert dBm in EIRP
2603 if ((strstr(string, "dBm")!=NULL) || (strstr(string,"dbm")!=NULL))
2604 LR.erp=(pow(10.0,(LR.erp-32.14)/10.0));
2610 if (forced_erp!=-1.0)
2613 if (forced_freq>=20.0 && forced_freq<=20000.0)
2614 LR.frq_mhz=forced_freq;
2620 if (fd==NULL && forced_read)
2622 /* Assign some default parameters
2623 for use in this run. */
2625 LR.eps_dielect=15.0;
2626 LR.sgm_conductivity=0.005;
2627 LR.eno_ns_surfref=301.0;
2635 /* Write them to a "splat.lrp" file. */
2637 outfile=fopen("splat.lrp","w");
2639 fprintf(outfile,"%.3f\t; Earth Dielectric Constant (Relative permittivity)\n",LR.eps_dielect);
2640 fprintf(outfile,"%.3f\t; Earth Conductivity (Siemens per meter)\n", LR.sgm_conductivity);
2641 fprintf(outfile,"%.3f\t; Atmospheric Bending Constant (N-Units)\n",LR.eno_ns_surfref);
2642 fprintf(outfile,"%.3f\t; Frequency in MHz (20 MHz to 20 GHz)\n", LR.frq_mhz);
2643 fprintf(outfile,"%d\t; Radio Climate\n",LR.radio_climate);
2644 fprintf(outfile,"%d\t; Polarization (0 = Horizontal, 1 = Vertical)\n", LR.pol);
2645 fprintf(outfile,"%.2f\t; Fraction of situations\n",LR.conf);
2646 fprintf(outfile,"%.2f\t; Fraction of time\n",LR.rel);
2647 fprintf(outfile,"%.2f\t; Transmitter Effective Radiated Power in Watts or dBm (optional)\n",LR.erp);
2648 fprintf(outfile,"\nPlease consult SPLAT! documentation for the meaning and use of this data.\n");
2654 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);
2657 else if (forced_read==0)
2660 if (forced_read && (fd==NULL || ok==0))
2662 LR.eps_dielect=15.0;
2663 LR.sgm_conductivity=0.005;
2664 LR.eno_ns_surfref=301.0;
2672 fprintf(stderr,"Longley-Rice default parameters have been assumed for this analysis.\n");
2677 return (return_value);
2680 void PlotPath(struct site source, struct site destination, char mask_value)
2682 /* This function analyzes the path between the source and
2683 destination locations. It determines which points along
2684 the path have line-of-sight visibility to the source.
2685 Points along with path having line-of-sight visibility
2686 to the source at an AGL altitude equal to that of the
2687 destination location are stored by setting bit 1 in the
2688 mask[][] array, which are displayed in green when PPM
2689 maps are later generated by SPLAT!. */
2693 register double cos_xmtr_angle, cos_test_angle, test_alt;
2694 double distance, rx_alt, tx_alt;
2696 ReadPath(source,destination);
2698 for (y=0; y<path.length; y++)
2700 /* Test this point only if it hasn't been already
2701 tested and found to be free of obstructions. */
2703 if ((GetMask(path.lat[y],path.lon[y])&mask_value)==0)
2705 distance=5280.0*path.distance[y];
2706 tx_alt=earthradius+source.alt+path.elevation[0];
2707 rx_alt=earthradius+destination.alt+path.elevation[y];
2709 /* Calculate the cosine of the elevation of the
2710 transmitter as seen at the temp rx point. */
2712 cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
2714 for (x=y, block=0; x>=0 && block==0; x--)
2716 distance=5280.0*(path.distance[y]-path.distance[x]);
2717 test_alt=earthradius+(path.elevation[x]==0.0?path.elevation[x]:path.elevation[x]+clutter);
2719 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
2721 /* Compare these two angles to determine if
2722 an obstruction exists. Since we're comparing
2723 the cosines of these angles rather than
2724 the angles themselves, the following "if"
2725 statement is reversed from what it would
2726 be if the actual angles were compared. */
2728 if (cos_xmtr_angle>=cos_test_angle)
2733 OrMask(path.lat[y],path.lon[y],mask_value);
2738 void PlotLRPath(struct site source, struct site destination, unsigned char mask_value, FILE *fd)
2740 /* This function plots the RF path loss between source and
2741 destination points based on the Longley-Rice propagation
2742 model, taking into account antenna pattern data, if available. */
2744 int x, y, ifs, ofs, errnum;
2745 char block=0, strmode[100];
2746 double loss, azimuth, pattern=0.0,
2747 xmtr_alt, dest_alt, xmtr_alt2, dest_alt2,
2748 cos_rcvr_angle, cos_test_angle=0.0, test_alt,
2749 elevation=0.0, distance=0.0, four_thirds_earth,
2750 field_strength=0.0, rxp, dBm;
2753 ReadPath(source,destination);
2755 four_thirds_earth=FOUR_THIRDS*EARTHRADIUS;
2757 /* Copy elevations plus clutter along path into the elev[] array. */
2759 for (x=1; x<path.length-1; x++)
2760 elev[x+2]=(path.elevation[x]==0.0?path.elevation[x]*METERS_PER_FOOT:(clutter+path.elevation[x])*METERS_PER_FOOT);
2762 /* Copy ending points without clutter */
2764 elev[2]=path.elevation[0]*METERS_PER_FOOT;
2765 elev[path.length+1]=path.elevation[path.length-1]*METERS_PER_FOOT;
2767 /* Since the only energy the Longley-Rice model considers
2768 reaching the destination is based on what is scattered
2769 or deflected from the first obstruction along the path,
2770 we first need to find the location and elevation angle
2771 of that first obstruction (if it exists). This is done
2772 using a 4/3rds Earth radius to match the model used by
2773 Longley-Rice. This information is required for properly
2774 integrating the antenna's elevation pattern into the
2775 calculation for overall path loss. */
2777 for (y=2; (y<(path.length-1) && path.distance[y]<=max_range); y++)
2779 /* Process this point only if it
2780 has not already been processed. */
2782 if ((GetMask(path.lat[y],path.lon[y])&248)!=(mask_value<<3))
2784 distance=5280.0*path.distance[y];
2785 xmtr_alt=four_thirds_earth+source.alt+path.elevation[0];
2786 dest_alt=four_thirds_earth+destination.alt+path.elevation[y];
2787 dest_alt2=dest_alt*dest_alt;
2788 xmtr_alt2=xmtr_alt*xmtr_alt;
2790 /* Calculate the cosine of the elevation of
2791 the receiver as seen by the transmitter. */
2793 cos_rcvr_angle=((xmtr_alt2)+(distance*distance)-(dest_alt2))/(2.0*xmtr_alt*distance);
2795 if (cos_rcvr_angle>1.0)
2798 if (cos_rcvr_angle<-1.0)
2799 cos_rcvr_angle=-1.0;
2801 if (got_elevation_pattern || fd!=NULL)
2803 /* Determine the elevation angle to the first obstruction
2804 along the path IF elevation pattern data is available
2805 or an output (.ano) file has been designated. */
2807 for (x=2, block=0; (x<y && block==0); x++)
2809 distance=5280.0*path.distance[x];
2811 test_alt=four_thirds_earth+(path.elevation[x]==0.0?path.elevation[x]:path.elevation[x]+clutter);
2813 /* Calculate the cosine of the elevation
2814 angle of the terrain (test point)
2815 as seen by the transmitter. */
2817 cos_test_angle=((xmtr_alt2)+(distance*distance)-(test_alt*test_alt))/(2.0*xmtr_alt*distance);
2819 if (cos_test_angle>1.0)
2822 if (cos_test_angle<-1.0)
2823 cos_test_angle=-1.0;
2825 /* Compare these two angles to determine if
2826 an obstruction exists. Since we're comparing
2827 the cosines of these angles rather than
2828 the angles themselves, the sense of the
2829 following "if" statement is reversed from
2830 what it would be if the angles themselves
2833 if (cos_rcvr_angle>=cos_test_angle)
2838 elevation=((acos(cos_test_angle))/DEG2RAD)-90.0;
2840 elevation=((acos(cos_rcvr_angle))/DEG2RAD)-90.0;
2843 /* Determine attenuation for each point along the
2844 path using Longley-Rice's point_to_point mode
2845 starting at y=2 (number_of_points = 1), the
2846 shortest distance terrain can play a role in
2849 elev[0]=y-1; /* (number of points - 1) */
2851 /* Distance between elevation samples */
2853 elev[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]);
2855 point_to_point(elev,source.alt*METERS_PER_FOOT,
2856 destination.alt*METERS_PER_FOOT, LR.eps_dielect,
2857 LR.sgm_conductivity, LR.eno_ns_surfref, LR.frq_mhz,
2858 LR.radio_climate, LR.pol, LR.conf, LR.rel, loss,
2861 temp.lat=path.lat[y];
2862 temp.lon=path.lon[y];
2864 azimuth=(Azimuth(source,temp));
2867 fprintf(fd,"%.7f, %.7f, %.3f, %.3f, ",path.lat[y], path.lon[y], azimuth, elevation);
2869 /* If ERP==0, write path loss to alphanumeric
2870 output file. Otherwise, write field strength
2871 or received power level (below), as appropriate. */
2873 if (fd!=NULL && LR.erp==0.0)
2874 fprintf(fd,"%.2f",loss);
2876 /* Integrate the antenna's radiation
2877 pattern into the overall path loss. */
2879 x=(int)rint(10.0*(10.0-elevation));
2881 if (x>=0 && x<=1000)
2883 azimuth=rint(azimuth);
2885 pattern=(double)LR.antenna_pattern[(int)azimuth][x];
2889 pattern=20.0*log10(pattern);
2898 /* dBm is based on EIRP (ERP + 2.14) */
2900 rxp=LR.erp/(pow(10.0,(loss-2.14)/10.0));
2902 dBm=10.0*(log10(rxp*1000.0));
2905 fprintf(fd,"%.3f",dBm);
2907 /* Scale roughly between 0 and 255 */
2909 ifs=200+(int)rint(dBm);
2917 ofs=GetSignal(path.lat[y],path.lon[y]);
2922 PutSignal(path.lat[y],path.lon[y],(unsigned char)ifs);
2927 field_strength=(139.4+(20.0*log10(LR.frq_mhz))-loss)+(10.0*log10(LR.erp/1000.0));
2929 ifs=100+(int)rint(field_strength);
2937 ofs=GetSignal(path.lat[y],path.lon[y]);
2942 PutSignal(path.lat[y],path.lon[y],(unsigned char)ifs);
2945 fprintf(fd,"%.3f",field_strength);
2954 ifs=(int)rint(loss);
2956 ofs=GetSignal(path.lat[y],path.lon[y]);
2958 if (ofs<ifs && ofs!=0)
2961 PutSignal(path.lat[y],path.lon[y],(unsigned char)ifs);
2972 /* Mark this point as having been analyzed */
2974 PutMask(path.lat[y],path.lon[y],(GetMask(path.lat[y],path.lon[y])&7)+(mask_value<<3));
2979 void PlotLOSMap(struct site source, double altitude)
2981 /* This function performs a 360 degree sweep around the
2982 transmitter site (source location), and plots the
2983 line-of-sight coverage of the transmitter on the SPLAT!
2984 generated topographic map based on a receiver located
2985 at the specified altitude (in feet AGL). Results
2986 are stored in memory, and written out in the form
2987 of a topographic map when the WritePPM() function
2988 is later invoked. */
2992 unsigned char symbol[4], x;
2993 double lat, lon, minwest, maxnorth, th;
2994 static unsigned char mask_value=1;
3003 fprintf(stdout,"\nComputing line-of-sight coverage of \"%s\" with an RX antenna\nat %.2f %s AGL",source.name,metric?altitude*METERS_PER_FOOT:altitude,metric?"meters":"feet");
3006 fprintf(stdout," and %.2f %s of ground clutter",metric?clutter*METERS_PER_FOOT:clutter,metric?"meters":"feet");
3008 fprintf(stdout,"...\n\n 0%c to 25%c ",37,37);
3011 /* th=pixels/degree divided by 64 loops per
3012 progress indicator symbol (.oOo) printed. */
3016 z=(int)(th*ReduceAngle(max_west-min_west));
3018 minwest=dpp+(double)min_west;
3019 maxnorth=(double)max_north-dpp;
3021 for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y))
3030 PlotPath(source,edge,mask_value);
3035 fprintf(stdout,"%c",symbol[x]);
3047 fprintf(stdout,"\n25%c to 50%c ",37,37);
3050 z=(int)(th*(double)(max_north-min_north));
3052 for (lat=maxnorth, x=0, y=0; lat>=(double)min_north; y++, lat=maxnorth-(dpp*(double)y))
3058 PlotPath(source,edge,mask_value);
3063 fprintf(stdout,"%c",symbol[x]);
3075 fprintf(stdout,"\n50%c to 75%c ",37,37);
3078 z=(int)(th*ReduceAngle(max_west-min_west));
3080 for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y))
3089 PlotPath(source,edge,mask_value);
3094 fprintf(stdout,"%c",symbol[x]);
3106 fprintf(stdout,"\n75%c to 100%c ",37,37);
3109 z=(int)(th*(double)(max_north-min_north));
3111 for (lat=(double)min_north, x=0, y=0; lat<(double)max_north; y++, lat=(double)min_north+(dpp*(double)y))
3117 PlotPath(source,edge,mask_value);
3122 fprintf(stdout,"%c",symbol[x]);
3133 fprintf(stdout,"\nDone!\n");
3136 /* Assign next mask value */
3153 void PlotLRMap(struct site source, double altitude, char *plo_filename)
3155 /* This function performs a 360 degree sweep around the
3156 transmitter site (source location), and plots the
3157 Longley-Rice attenuation on the SPLAT! generated
3158 topographic map based on a receiver located at
3159 the specified altitude (in feet AGL). Results
3160 are stored in memory, and written out in the form
3161 of a topographic map when the WritePPMLR() or
3162 WritePPMSS() functions are later invoked. */
3166 double lat, lon, minwest, maxnorth, th;
3167 unsigned char x, symbol[4];
3168 static unsigned char mask_value=1;
3171 minwest=dpp+(double)min_west;
3172 maxnorth=(double)max_north-dpp;
3181 fprintf(stdout,"\nComputing Longley-Rice ");
3184 fprintf(stdout,"path loss");
3188 fprintf(stdout,"signal power level");
3190 fprintf(stdout,"field strength");
3193 fprintf(stdout," contours of \"%s\"\nout to a radius of %.2f %s with an RX antenna at %.2f %s AGL",source.name,metric?max_range*KM_PER_MILE:max_range,metric?"kilometers":"miles",metric?altitude*METERS_PER_FOOT:altitude,metric?"meters":"feet");
3196 fprintf(stdout,"\nand %.2f %s of ground clutter",metric?clutter*METERS_PER_FOOT:clutter,metric?"meters":"feet");
3198 fprintf(stdout,"...\n\n 0%c to 25%c ",37,37);
3201 if (plo_filename[0]!=0)
3202 fd=fopen(plo_filename,"wb");
3206 /* Write header information to output file */
3208 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);
3211 /* th=pixels/degree divided by 64 loops per
3212 progress indicator symbol (.oOo) printed. */
3216 z=(int)(th*ReduceAngle(max_west-min_west));
3218 for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y))
3227 PlotLRPath(source,edge,mask_value,fd);
3232 fprintf(stdout,"%c",symbol[x]);
3244 fprintf(stdout,"\n25%c to 50%c ",37,37);
3247 z=(int)(th*(double)(max_north-min_north));
3249 for (lat=maxnorth, x=0, y=0; lat>=(double)min_north; y++, lat=maxnorth-(dpp*(double)y))
3255 PlotLRPath(source,edge,mask_value,fd);
3260 fprintf(stdout,"%c",symbol[x]);
3272 fprintf(stdout,"\n50%c to 75%c ",37,37);
3275 z=(int)(th*ReduceAngle(max_west-min_west));
3277 for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y))
3286 PlotLRPath(source,edge,mask_value,fd);
3291 fprintf(stdout,"%c",symbol[x]);
3303 fprintf(stdout,"\n75%c to 100%c ",37,37);
3306 z=(int)(th*(double)(max_north-min_north));
3308 for (lat=(double)min_north, x=0, y=0; lat<(double)max_north; y++, lat=(double)min_north+(dpp*(double)y))
3314 PlotLRPath(source,edge,mask_value,fd);
3319 fprintf(stdout,"%c",symbol[x]);
3333 fprintf(stdout,"\nDone!\n");
3340 void LoadSignalColors(struct site xmtr)
3342 int x, y, ok, val[4];
3343 char filename[255], string[80], *pointer=NULL, *s=NULL;
3346 for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++)
3347 filename[x]=xmtr.filename[x];
3355 /* Default values */
3357 region.level[0]=128;
3358 region.color[0][0]=255;
3359 region.color[0][1]=0;
3360 region.color[0][2]=0;
3362 region.level[1]=118;
3363 region.color[1][0]=255;
3364 region.color[1][1]=165;
3365 region.color[1][2]=0;
3367 region.level[2]=108;
3368 region.color[2][0]=255;
3369 region.color[2][1]=206;
3370 region.color[2][2]=0;
3373 region.color[3][0]=255;
3374 region.color[3][1]=255;
3375 region.color[3][2]=0;
3378 region.color[4][0]=184;
3379 region.color[4][1]=255;
3380 region.color[4][2]=0;
3383 region.color[5][0]=0;
3384 region.color[5][1]=255;
3385 region.color[5][2]=0;
3388 region.color[6][0]=0;
3389 region.color[6][1]=208;
3390 region.color[6][2]=0;
3393 region.color[7][0]=0;
3394 region.color[7][1]=196;
3395 region.color[7][2]=196;
3398 region.color[8][0]=0;
3399 region.color[8][1]=148;
3400 region.color[8][2]=255;
3403 region.color[9][0]=80;
3404 region.color[9][1]=80;
3405 region.color[9][2]=255;
3407 region.level[10]=28;
3408 region.color[10][0]=0;
3409 region.color[10][1]=38;
3410 region.color[10][2]=255;
3412 region.level[11]=18;
3413 region.color[11][0]=142;
3414 region.color[11][1]=63;
3415 region.color[11][2]=255;
3418 region.color[12][0]=140;
3419 region.color[12][1]=0;
3420 region.color[12][2]=128;
3424 fd=fopen("splat.scf","r");
3427 fd=fopen(filename,"r");
3431 fd=fopen(filename,"w");
3433 fprintf(fd,"; SPLAT! Auto-generated Signal Color Definition (\"%s\") File\n",filename);
3434 fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n");
3435 fprintf(fd,"; dBuV/m: red, green, blue\n;\n");
3436 fprintf(fd,"; ...where \"dBuV/m\" is the signal strength (in dBuV/m) and\n");
3437 fprintf(fd,"; \"red\", \"green\", and \"blue\" are the corresponding RGB color\n");
3438 fprintf(fd,"; definitions ranging from 0 to 255 for the region specified.\n");
3439 fprintf(fd,";\n; The following parameters may be edited and/or expanded\n");
3440 fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n");
3441 fprintf(fd,"; may be defined in this file.\n;\n;\n");
3443 for (x=0; x<region.levels; x++)
3444 fprintf(fd,"%3d: %3d, %3d, %3d\n",region.level[x], region.color[x][0], region.color[x][1], region.color[x][2]);
3452 s=fgets(string,80,fd);
3454 while (x<32 && feof(fd)==0)
3456 pointer=strchr(string,';');
3461 ok=sscanf(string,"%d: %d, %d, %d", &val[0], &val[1], &val[2], &val[3]);
3474 region.level[x]=val[0];
3475 region.color[x][0]=val[1];
3476 region.color[x][1]=val[2];
3477 region.color[x][2]=val[3];
3481 s=fgets(string,80,fd);
3489 void LoadLossColors(struct site xmtr)
3491 int x, y, ok, val[4];
3492 char filename[255], string[80], *pointer=NULL, *s=NULL;
3495 for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++)
3496 filename[x]=xmtr.filename[x];
3504 /* Default values */
3507 region.color[0][0]=255;
3508 region.color[0][1]=0;
3509 region.color[0][2]=0;
3512 region.color[1][0]=255;
3513 region.color[1][1]=128;
3514 region.color[1][2]=0;
3516 region.level[2]=100;
3517 region.color[2][0]=255;
3518 region.color[2][1]=165;
3519 region.color[2][2]=0;
3521 region.level[3]=110;
3522 region.color[3][0]=255;
3523 region.color[3][1]=206;
3524 region.color[3][2]=0;
3526 region.level[4]=120;
3527 region.color[4][0]=255;
3528 region.color[4][1]=255;
3529 region.color[4][2]=0;
3531 region.level[5]=130;
3532 region.color[5][0]=184;
3533 region.color[5][1]=255;
3534 region.color[5][2]=0;
3536 region.level[6]=140;
3537 region.color[6][0]=0;
3538 region.color[6][1]=255;
3539 region.color[6][2]=0;
3541 region.level[7]=150;
3542 region.color[7][0]=0;
3543 region.color[7][1]=208;
3544 region.color[7][2]=0;
3546 region.level[8]=160;
3547 region.color[8][0]=0;
3548 region.color[8][1]=196;
3549 region.color[8][2]=196;
3551 region.level[9]=170;
3552 region.color[9][0]=0;
3553 region.color[9][1]=148;
3554 region.color[9][2]=255;
3556 region.level[10]=180;
3557 region.color[10][0]=80;
3558 region.color[10][1]=80;
3559 region.color[10][2]=255;
3561 region.level[11]=190;
3562 region.color[11][0]=0;
3563 region.color[11][1]=38;
3564 region.color[11][2]=255;
3566 region.level[12]=200;
3567 region.color[12][0]=142;
3568 region.color[12][1]=63;
3569 region.color[12][2]=255;
3571 region.level[13]=210;
3572 region.color[13][0]=196;
3573 region.color[13][1]=54;
3574 region.color[13][2]=255;
3576 region.level[14]=220;
3577 region.color[14][0]=255;
3578 region.color[14][1]=0;
3579 region.color[14][2]=255;
3581 region.level[15]=230;
3582 region.color[15][0]=255;
3583 region.color[15][1]=194;
3584 region.color[15][2]=204;
3588 fd=fopen("splat.lcf","r");
3591 fd=fopen(filename,"r");
3595 fd=fopen(filename,"w");
3597 fprintf(fd,"; SPLAT! Auto-generated Path-Loss Color Definition (\"%s\") File\n",filename);
3598 fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n");
3599 fprintf(fd,"; dB: red, green, blue\n;\n");
3600 fprintf(fd,"; ...where \"dB\" is the path loss (in dB) and\n");
3601 fprintf(fd,"; \"red\", \"green\", and \"blue\" are the corresponding RGB color\n");
3602 fprintf(fd,"; definitions ranging from 0 to 255 for the region specified.\n");
3603 fprintf(fd,";\n; The following parameters may be edited and/or expanded\n");
3604 fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n");
3605 fprintf(fd,"; may be defined in this file.\n;\n;\n");
3607 for (x=0; x<region.levels; x++)
3608 fprintf(fd,"%3d: %3d, %3d, %3d\n",region.level[x], region.color[x][0], region.color[x][1], region.color[x][2]);
3616 s=fgets(string,80,fd);
3618 while (x<32 && feof(fd)==0)
3620 pointer=strchr(string,';');
3625 ok=sscanf(string,"%d: %d, %d, %d", &val[0], &val[1], &val[2], &val[3]);
3638 region.level[x]=val[0];
3639 region.color[x][0]=val[1];
3640 region.color[x][1]=val[2];
3641 region.color[x][2]=val[3];
3645 s=fgets(string,80,fd);
3653 void LoadDBMColors(struct site xmtr)
3655 int x, y, ok, val[4];
3656 char filename[255], string[80], *pointer=NULL, *s=NULL;
3659 for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++)
3660 filename[x]=xmtr.filename[x];
3668 /* Default values */
3671 region.color[0][0]=255;
3672 region.color[0][1]=0;
3673 region.color[0][2]=0;
3675 region.level[1]=-10;
3676 region.color[1][0]=255;
3677 region.color[1][1]=128;
3678 region.color[1][2]=0;
3680 region.level[2]=-20;
3681 region.color[2][0]=255;
3682 region.color[2][1]=165;
3683 region.color[2][2]=0;
3685 region.level[3]=-30;
3686 region.color[3][0]=255;
3687 region.color[3][1]=206;
3688 region.color[3][2]=0;
3690 region.level[4]=-40;
3691 region.color[4][0]=255;
3692 region.color[4][1]=255;
3693 region.color[4][2]=0;
3695 region.level[5]=-50;
3696 region.color[5][0]=184;
3697 region.color[5][1]=255;
3698 region.color[5][2]=0;
3700 region.level[6]=-60;
3701 region.color[6][0]=0;
3702 region.color[6][1]=255;
3703 region.color[6][2]=0;
3705 region.level[7]=-70;
3706 region.color[7][0]=0;
3707 region.color[7][1]=208;
3708 region.color[7][2]=0;
3710 region.level[8]=-80;
3711 region.color[8][0]=0;
3712 region.color[8][1]=196;
3713 region.color[8][2]=196;
3715 region.level[9]=-90;
3716 region.color[9][0]=0;
3717 region.color[9][1]=148;
3718 region.color[9][2]=255;
3720 region.level[10]=-100;
3721 region.color[10][0]=80;
3722 region.color[10][1]=80;
3723 region.color[10][2]=255;
3725 region.level[11]=-110;
3726 region.color[11][0]=0;
3727 region.color[11][1]=38;
3728 region.color[11][2]=255;
3730 region.level[12]=-120;
3731 region.color[12][0]=142;
3732 region.color[12][1]=63;
3733 region.color[12][2]=255;
3735 region.level[13]=-130;
3736 region.color[13][0]=196;
3737 region.color[13][1]=54;
3738 region.color[13][2]=255;
3740 region.level[14]=-140;
3741 region.color[14][0]=255;
3742 region.color[14][1]=0;
3743 region.color[14][2]=255;
3745 region.level[15]=-150;
3746 region.color[15][0]=255;
3747 region.color[15][1]=194;
3748 region.color[15][2]=204;
3752 fd=fopen("splat.dcf","r");
3755 fd=fopen(filename,"r");
3759 fd=fopen(filename,"w");
3761 fprintf(fd,"; SPLAT! Auto-generated DBM Signal Level Color Definition (\"%s\") File\n",filename);
3762 fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n");
3763 fprintf(fd,"; dBm: red, green, blue\n;\n");
3764 fprintf(fd,"; ...where \"dBm\" is the received signal power level between +40 dBm\n");
3765 fprintf(fd,"; and -200 dBm, and \"red\", \"green\", and \"blue\" are the corresponding\n");
3766 fprintf(fd,"; RGB color definitions ranging from 0 to 255 for the region specified.\n");
3767 fprintf(fd,";\n; The following parameters may be edited and/or expanded\n");
3768 fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n");
3769 fprintf(fd,"; may be defined in this file.\n;\n;\n");
3771 for (x=0; x<region.levels; x++)
3772 fprintf(fd,"%+4d: %3d, %3d, %3d\n",region.level[x], region.color[x][0], region.color[x][1], region.color[x][2]);
3780 s=fgets(string,80,fd);
3782 while (x<32 && feof(fd)==0)
3784 pointer=strchr(string,';');
3789 ok=sscanf(string,"%d: %d, %d, %d", &val[0], &val[1], &val[2], &val[3]);
3799 region.level[x]=val[0];
3810 region.color[x][0]=val[1];
3811 region.color[x][1]=val[2];
3812 region.color[x][2]=val[3];
3816 s=fgets(string,80,fd);
3824 void WritePPM(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
3826 /* This function generates a topographic map in Portable Pix Map
3827 (PPM) format based on logarithmically scaled topology data,
3828 as well as the content of flags held in the mask[][] array.
3829 The image created is rotated counter-clockwise 90 degrees
3830 from its representation in dem[][] so that north points
3831 up and east points right in the image generated. */
3833 char mapfile[255], geofile[255], kmlfile[255];
3834 unsigned char found, mask;
3835 unsigned width, height, terrain;
3836 int indx, x, y, x0=0, y0=0;
3837 double lat, lon, conversion, one_over_gamma,
3838 north, south, east, west, minwest;
3841 one_over_gamma=1.0/GAMMA;
3842 conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
3844 width=(unsigned)(ippd*ReduceAngle(max_west-min_west));
3845 height=(unsigned)(ippd*ReduceAngle(max_north-min_north));
3849 strncpy(filename, xmtr[0].filename,254);
3850 filename[strlen(filename)-4]=0; /* Remove .qth */
3857 if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.')
3863 mapfile[x]=filename[x];
3864 geofile[x]=filename[x];
3865 kmlfile[x]=filename[x];
3884 minwest=((double)min_west)+dpp;
3889 north=(double)max_north-dpp;
3890 south=(double)min_north;
3891 east=(minwest<180.0?-minwest:360.0-min_west);
3892 west=(double)(max_west<180?-max_west:360-max_west);
3896 fd=fopen(geofile,"wb");
3898 fprintf(fd,"FILENAME\t%s\n",mapfile);
3899 fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
3900 fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north);
3901 fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south);
3902 fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height);
3903 fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version);
3910 fd=fopen(kmlfile,"wb");
3912 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
3913 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
3914 fprintf(fd," <Folder>\n");
3915 fprintf(fd," <name>%s</name>\n",splat_name);
3916 fprintf(fd," <description>Line-of-Sight Overlay</description>\n");
3917 fprintf(fd," <GroundOverlay>\n");
3918 fprintf(fd," <name>%s Line-of-Sight Overlay</name>\n",splat_name);
3919 fprintf(fd," <description>SPLAT! Coverage</description>\n");
3920 fprintf(fd," <Icon>\n");
3921 fprintf(fd," <href>%s</href>\n",mapfile);
3922 fprintf(fd," </Icon>\n");
3923 fprintf(fd," <opacity>128</opacity>\n");
3924 fprintf(fd," <LatLonBox>\n");
3925 fprintf(fd," <north>%.5f</north>\n",north);
3926 fprintf(fd," <south>%.5f</south>\n",south);
3927 fprintf(fd," <east>%.5f</east>\n",east);
3928 fprintf(fd," <west>%.5f</west>\n",west);
3929 fprintf(fd," <rotation>0.0</rotation>\n");
3930 fprintf(fd," </LatLonBox>\n");
3931 fprintf(fd," </GroundOverlay>\n");
3933 for (x=0; x<txsites; x++)
3935 fprintf(fd," <Placemark>\n");
3936 fprintf(fd," <name>%s</name>\n",xmtr[x].name);
3937 fprintf(fd," <visibility>1</visibility>\n");
3938 fprintf(fd," <Style>\n");
3939 fprintf(fd," <IconStyle>\n");
3940 fprintf(fd," <Icon>\n");
3941 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
3942 fprintf(fd," <x>224</x>\n");
3943 fprintf(fd," <y>224</y>\n");
3944 fprintf(fd," <w>32</w>\n");
3945 fprintf(fd," <h>32</h>\n");
3946 fprintf(fd," </Icon>\n");
3947 fprintf(fd," </IconStyle>\n");
3948 fprintf(fd," </Style>\n");
3949 fprintf(fd," <Point>\n");
3950 fprintf(fd," <extrude>1</extrude>\n");
3951 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
3952 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);
3953 fprintf(fd," </Point>\n");
3954 fprintf(fd," </Placemark>\n");
3959 fprintf(fd," </Folder>\n");
3960 fprintf(fd,"</kml>\n");
3965 fd=fopen(mapfile,"wb");
3967 fprintf(fd,"P6\n%u %u\n255\n",width,height);
3968 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height);
3971 for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y))
3973 for (x=0, lon=max_west; x<(int)width; x++, lon=(double)max_west-(dpp*(double)x))
3978 for (indx=0, found=0; indx<MAXPAGES && found==0;)
3980 x0=(int)rint(ppd*(lat-(double)dem[indx].min_north));
3981 y0=mpi-(int)rint(ppd*(LonDiff((double)dem[indx].max_west,lon)));
3983 if (x0>=0 && x0<=mpi && y0>=0 && y0<=mpi)
3991 mask=dem[indx].mask[x0][y0];
3994 /* Text Labels: Red */
3995 fprintf(fd,"%c%c%c",255,0,0);
3998 /* County Boundaries: Light Cyan */
3999 fprintf(fd,"%c%c%c",128,128,255);
4001 else switch (mask&57)
4005 fprintf(fd,"%c%c%c",0,255,0);
4010 fprintf(fd,"%c%c%c",0,255,255);
4014 /* TX1 + TX2: Yellow */
4015 fprintf(fd,"%c%c%c",255,255,0);
4019 /* TX3: Medium Violet */
4020 fprintf(fd,"%c%c%c",147,112,219);
4024 /* TX1 + TX3: Pink */
4025 fprintf(fd,"%c%c%c",255,192,203);
4029 /* TX2 + TX3: Orange */
4030 fprintf(fd,"%c%c%c",255,165,0);
4034 /* TX1 + TX2 + TX3: Dark Green */
4035 fprintf(fd,"%c%c%c",0,100,0);
4040 fprintf(fd,"%c%c%c",255,130,71);
4044 /* TX1 + TX4: Green Yellow */
4045 fprintf(fd,"%c%c%c",173,255,47);
4049 /* TX2 + TX4: Dark Sea Green 1 */
4050 fprintf(fd,"%c%c%c",193,255,193);
4054 /* TX1 + TX2 + TX4: Blanched Almond */
4055 fprintf(fd,"%c%c%c",255,235,205);
4059 /* TX3 + TX4: Dark Turquoise */
4060 fprintf(fd,"%c%c%c",0,206,209);
4064 /* TX1 + TX3 + TX4: Medium Spring Green */
4065 fprintf(fd,"%c%c%c",0,250,154);
4069 /* TX2 + TX3 + TX4: Tan */
4070 fprintf(fd,"%c%c%c",210,180,140);
4074 /* TX1 + TX2 + TX3 + TX4: Gold2 */
4075 fprintf(fd,"%c%c%c",238,201,0);
4079 if (ngs) /* No terrain */
4080 fprintf(fd,"%c%c%c",255,255,255);
4083 /* Sea-level: Medium Blue */
4084 if (dem[indx].data[x0][y0]==0)
4085 fprintf(fd,"%c%c%c",0,0,170);
4088 /* Elevation: Greyscale */
4089 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
4090 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
4098 /* We should never get here, but if */
4099 /* we do, display the region as black */
4101 fprintf(fd,"%c%c%c",0,0,0);
4107 fprintf(stdout,"Done!\n");
4111 void WritePPMLR(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
4113 /* This function generates a topographic map in Portable Pix Map
4114 (PPM) format based on the content of flags held in the mask[][]
4115 array (only). The image created is rotated counter-clockwise
4116 90 degrees from its representation in dem[][] so that north
4117 points up and east points right in the image generated. */
4119 char mapfile[255], geofile[255], kmlfile[255];
4120 unsigned width, height, red, green, blue, terrain=0;
4121 unsigned char found, mask, cityorcounty;
4122 int indx, x, y, z, colorwidth, x0, y0, loss, level,
4123 hundreds, tens, units, match;
4124 double lat, lon, conversion, one_over_gamma,
4125 north, south, east, west, minwest;
4128 one_over_gamma=1.0/GAMMA;
4129 conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
4131 width=(unsigned)(ippd*ReduceAngle(max_west-min_west));
4132 height=(unsigned)(ippd*ReduceAngle(max_north-min_north));
4134 LoadLossColors(xmtr[0]);
4138 strncpy(filename, xmtr[0].filename,254);
4139 filename[strlen(filename)-4]=0; /* Remove .qth */
4146 if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.')
4152 mapfile[x]=filename[x];
4153 geofile[x]=filename[x];
4154 kmlfile[x]=filename[x];
4173 minwest=((double)min_west)+dpp;
4178 north=(double)max_north-dpp;
4181 south=(double)min_north; /* No bottom legend */
4183 south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */
4185 east=(minwest<180.0?-minwest:360.0-min_west);
4186 west=(double)(max_west<180?-max_west:360-max_west);
4190 fd=fopen(geofile,"wb");
4192 fprintf(fd,"FILENAME\t%s\n",mapfile);
4193 fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
4194 fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north);
4196 fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south);
4197 fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height);
4199 fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version);
4206 fd=fopen(kmlfile,"wb");
4208 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
4209 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
4210 fprintf(fd,"<!-- Generated by %s Version %s -->\n",splat_name,splat_version);
4211 fprintf(fd," <Folder>\n");
4212 fprintf(fd," <name>%s</name>\n",splat_name);
4213 fprintf(fd," <description>%s Transmitter Path Loss Overlay</description>\n",xmtr[0].name);
4214 fprintf(fd," <GroundOverlay>\n");
4215 fprintf(fd," <name>SPLAT! Path Loss Overlay</name>\n");
4216 fprintf(fd," <description>SPLAT! Coverage</description>\n");
4217 fprintf(fd," <Icon>\n");
4218 fprintf(fd," <href>%s</href>\n",mapfile);
4219 fprintf(fd," </Icon>\n");
4220 fprintf(fd," <opacity>128</opacity>\n");
4221 fprintf(fd," <LatLonBox>\n");
4222 fprintf(fd," <north>%.5f</north>\n",north);
4223 fprintf(fd," <south>%.5f</south>\n",south);
4224 fprintf(fd," <east>%.5f</east>\n",east);
4225 fprintf(fd," <west>%.5f</west>\n",west);
4226 fprintf(fd," <rotation>0.0</rotation>\n");
4227 fprintf(fd," </LatLonBox>\n");
4228 fprintf(fd," </GroundOverlay>\n");
4230 for (x=0; x<txsites; x++)
4232 fprintf(fd," <Placemark>\n");
4233 fprintf(fd," <name>%s</name>\n",xmtr[x].name);
4234 fprintf(fd," <visibility>1</visibility>\n");
4235 fprintf(fd," <Style>\n");
4236 fprintf(fd," <IconStyle>\n");
4237 fprintf(fd," <Icon>\n");
4238 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
4239 fprintf(fd," <x>224</x>\n");
4240 fprintf(fd," <y>224</y>\n");
4241 fprintf(fd," <w>32</w>\n");
4242 fprintf(fd," <h>32</h>\n");
4243 fprintf(fd," </Icon>\n");
4244 fprintf(fd," </IconStyle>\n");
4245 fprintf(fd," </Style>\n");
4246 fprintf(fd," <Point>\n");
4247 fprintf(fd," <extrude>1</extrude>\n");
4248 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
4249 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);
4250 fprintf(fd," </Point>\n");
4251 fprintf(fd," </Placemark>\n");
4254 fprintf(fd," </Folder>\n");
4255 fprintf(fd,"</kml>\n");
4260 fd=fopen(mapfile,"wb");
4262 fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
4263 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
4266 for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y))
4268 for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x))
4273 for (indx=0, found=0; indx<MAXPAGES && found==0;)
4275 x0=(int)rint(ppd*(lat-(double)dem[indx].min_north));
4276 y0=mpi-(int)rint(ppd*(LonDiff((double)dem[indx].max_west,lon)));
4278 if (x0>=0 && x0<=mpi && y0>=0 && y0<=mpi)
4286 mask=dem[indx].mask[x0][y0];
4287 loss=(dem[indx].signal[x0][y0]);
4296 if (loss<=region.level[0])
4300 for (z=1; (z<region.levels && match==255); z++)
4302 if (loss>=region.level[z-1] && loss<region.level[z])
4307 if (match<region.levels)
4309 red=region.color[match][0];
4310 green=region.color[match][1];
4311 blue=region.color[match][2];
4316 /* Text Labels: Red or otherwise */
4318 if (red>=180 && green<=75 && blue<=75 && loss!=0)
4319 fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
4321 fprintf(fd,"%c%c%c",255,0,0);
4328 /* County Boundaries: Black */
4330 fprintf(fd,"%c%c%c",0,0,0);
4335 if (cityorcounty==0)
4337 if (loss==0 || (contour_threshold!=0 && loss>abs(contour_threshold)))
4339 if (ngs) /* No terrain */
4340 fprintf(fd,"%c%c%c",255,255,255);
4343 /* Display land or sea elevation */
4345 if (dem[indx].data[x0][y0]==0)
4346 fprintf(fd,"%c%c%c",0,0,170);
4349 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
4350 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
4357 /* Plot path loss in color */
4359 if (red!=0 || green!=0 || blue!=0)
4360 fprintf(fd,"%c%c%c",red,green,blue);
4362 else /* terrain / sea-level */
4364 if (dem[indx].data[x0][y0]==0)
4365 fprintf(fd,"%c%c%c",0,0,170);
4368 /* Elevation: Greyscale */
4369 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
4370 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
4379 /* We should never get here, but if */
4380 /* we do, display the region as black */
4382 fprintf(fd,"%c%c%c",0,0,0);
4387 if (kml==0 && geo==0)
4389 /* Display legend along bottom of image
4390 * if not generating .kml or .geo output.
4393 colorwidth=(int)rint((float)width/(float)region.levels);
4395 for (y0=0; y0<30; y0++)
4397 for (x0=0; x0<(int)width; x0++)
4401 level=region.level[indx];
4406 level-=(hundreds*100);
4415 if (y0>=8 && y0<=23)
4420 if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-11)))
4424 if (tens>0 || hundreds>0)
4427 if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-19)))
4432 if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-27)))
4436 if (fontdata[16*('d')+(y0-8)]&(128>>(x-42)))
4440 if (fontdata[16*('B')+(y0-8)]&(128>>(x-50)))
4444 if (indx>region.levels)
4445 fprintf(fd,"%c%c%c",0,0,0);
4448 red=region.color[indx][0];
4449 green=region.color[indx][1];
4450 blue=region.color[indx][2];
4452 fprintf(fd,"%c%c%c",red,green,blue);
4459 fprintf(stdout,"Done!\n");
4463 void WritePPMSS(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
4465 /* This function generates a topographic map in Portable Pix Map
4466 (PPM) format based on the signal strength values held in the
4467 signal[][] array. The image created is rotated counter-clockwise
4468 90 degrees from its representation in dem[][] so that north
4469 points up and east points right in the image generated. */
4471 char mapfile[255], geofile[255], kmlfile[255];
4472 unsigned width, height, terrain, red, green, blue;
4473 unsigned char found, mask, cityorcounty;
4474 int indx, x, y, z=1, x0, y0, signal, level, hundreds,
4475 tens, units, match, colorwidth;
4476 double conversion, one_over_gamma, lat, lon,
4477 north, south, east, west, minwest;
4480 one_over_gamma=1.0/GAMMA;
4481 conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
4483 width=(unsigned)(ippd*ReduceAngle(max_west-min_west));
4484 height=(unsigned)(ippd*ReduceAngle(max_north-min_north));
4486 LoadSignalColors(xmtr[0]);
4490 strncpy(filename, xmtr[0].filename,254);
4491 filename[strlen(filename)-4]=0; /* Remove .qth */
4498 if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.')
4504 mapfile[x]=filename[x];
4505 geofile[x]=filename[x];
4506 kmlfile[x]=filename[x];
4525 minwest=((double)min_west)+dpp;
4530 north=(double)max_north-dpp;
4533 south=(double)min_north; /* No bottom legend */
4535 south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */
4537 east=(minwest<180.0?-minwest:360.0-min_west);
4538 west=(double)(max_west<180?-max_west:360-max_west);
4542 fd=fopen(geofile,"wb");
4544 fprintf(fd,"FILENAME\t%s\n",mapfile);
4545 fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
4546 fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north);
4548 fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south);
4549 fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height);
4551 fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version);
4558 fd=fopen(kmlfile,"wb");
4560 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
4561 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
4562 fprintf(fd,"<!-- Generated by %s Version %s -->\n",splat_name,splat_version);
4563 fprintf(fd," <Folder>\n");
4564 fprintf(fd," <name>%s</name>\n",splat_name);
4565 fprintf(fd," <description>%s Transmitter Coverage Overlay</description>\n",xmtr[0].name);
4566 fprintf(fd," <GroundOverlay>\n");
4567 fprintf(fd," <name>SPLAT! Signal Strength Overlay</name>\n");
4568 fprintf(fd," <description>SPLAT! Coverage</description>\n");
4569 fprintf(fd," <Icon>\n");
4570 fprintf(fd," <href>%s</href>\n",mapfile);
4571 fprintf(fd," </Icon>\n");
4572 fprintf(fd," <opacity>128</opacity>\n");
4573 fprintf(fd," <LatLonBox>\n");
4574 fprintf(fd," <north>%.5f</north>\n",north);
4575 fprintf(fd," <south>%.5f</south>\n",south);
4576 fprintf(fd," <east>%.5f</east>\n",east);
4577 fprintf(fd," <west>%.5f</west>\n",west);
4578 fprintf(fd," <rotation>0.0</rotation>\n");
4579 fprintf(fd," </LatLonBox>\n");
4580 fprintf(fd," </GroundOverlay>\n");
4582 for (x=0; x<txsites; x++)
4584 fprintf(fd," <Placemark>\n");
4585 fprintf(fd," <name>%s</name>\n",xmtr[x].name);
4586 fprintf(fd," <visibility>1</visibility>\n");
4587 fprintf(fd," <Style>\n");
4588 fprintf(fd," <IconStyle>\n");
4589 fprintf(fd," <Icon>\n");
4590 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
4591 fprintf(fd," <x>224</x>\n");
4592 fprintf(fd," <y>224</y>\n");
4593 fprintf(fd," <w>32</w>\n");
4594 fprintf(fd," <h>32</h>\n");
4595 fprintf(fd," </Icon>\n");
4596 fprintf(fd," </IconStyle>\n");
4597 fprintf(fd," </Style>\n");
4598 fprintf(fd," <Point>\n");
4599 fprintf(fd," <extrude>1</extrude>\n");
4600 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
4601 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);
4602 fprintf(fd," </Point>\n");
4603 fprintf(fd," </Placemark>\n");
4606 fprintf(fd," </Folder>\n");
4607 fprintf(fd,"</kml>\n");
4612 fd=fopen(mapfile,"wb");
4614 fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
4615 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
4618 for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y))
4620 for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x))
4625 for (indx=0, found=0; indx<MAXPAGES && found==0;)
4627 x0=(int)rint(ppd*(lat-(double)dem[indx].min_north));
4628 y0=mpi-(int)rint(ppd*(LonDiff((double)dem[indx].max_west,lon)));
4630 if (x0>=0 && x0<=mpi && y0>=0 && y0<=mpi)
4638 mask=dem[indx].mask[x0][y0];
4639 signal=(dem[indx].signal[x0][y0])-100;
4648 if (signal>=region.level[0])
4652 for (z=1; (z<region.levels && match==255); z++)
4654 if (signal<region.level[z-1] && signal>=region.level[z])
4659 if (match<region.levels)
4661 red=region.color[match][0];
4662 green=region.color[match][1];
4663 blue=region.color[match][2];
4668 /* Text Labels: Red or otherwise */
4670 if (red>=180 && green<=75 && blue<=75)
4671 fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
4673 fprintf(fd,"%c%c%c",255,0,0);
4680 /* County Boundaries: Black */
4682 fprintf(fd,"%c%c%c",0,0,0);
4687 if (cityorcounty==0)
4689 if (contour_threshold!=0 && signal<contour_threshold)
4692 fprintf(fd,"%c%c%c",255,255,255);
4695 /* Display land or sea elevation */
4697 if (dem[indx].data[x0][y0]==0)
4698 fprintf(fd,"%c%c%c",0,0,170);
4701 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
4702 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
4709 /* Plot field strength regions in color */
4711 if (red!=0 || green!=0 || blue!=0)
4712 fprintf(fd,"%c%c%c",red,green,blue);
4714 else /* terrain / sea-level */
4717 fprintf(fd,"%c%c%c",255,255,255);
4720 if (dem[indx].data[x0][y0]==0)
4721 fprintf(fd,"%c%c%c",0,0,170);
4724 /* Elevation: Greyscale */
4725 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
4726 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
4736 /* We should never get here, but if */
4737 /* we do, display the region as black */
4739 fprintf(fd,"%c%c%c",0,0,0);
4744 if (kml==0 && geo==0)
4746 /* Display legend along bottom of image
4747 * if not generating .kml or .geo output.
4750 colorwidth=(int)rint((float)width/(float)region.levels);
4752 for (y0=0; y0<30; y0++)
4754 for (x0=0; x0<(int)width; x0++)
4758 level=region.level[indx];
4763 level-=(hundreds*100);
4772 if (y0>=8 && y0<=23)
4777 if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-5)))
4781 if (tens>0 || hundreds>0)
4784 if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-13)))
4789 if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-21)))
4793 if (fontdata[16*('d')+(y0-8)]&(128>>(x-36)))
4797 if (fontdata[16*('B')+(y0-8)]&(128>>(x-44)))
4801 if (fontdata[16*(230)+(y0-8)]&(128>>(x-52)))
4805 if (fontdata[16*('V')+(y0-8)]&(128>>(x-60)))
4809 if (fontdata[16*('/')+(y0-8)]&(128>>(x-68)))
4813 if (fontdata[16*('m')+(y0-8)]&(128>>(x-76)))
4817 if (indx>region.levels)
4818 fprintf(fd,"%c%c%c",0,0,0);
4821 red=region.color[indx][0];
4822 green=region.color[indx][1];
4823 blue=region.color[indx][2];
4825 fprintf(fd,"%c%c%c",red,green,blue);
4832 fprintf(stdout,"Done!\n");
4836 void WritePPMDBM(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
4838 /* This function generates a topographic map in Portable Pix Map
4839 (PPM) format based on the signal power level values held in the
4840 signal[][] array. The image created is rotated counter-clockwise
4841 90 degrees from its representation in dem[][] so that north
4842 points up and east points right in the image generated. */
4844 char mapfile[255], geofile[255], kmlfile[255];
4845 unsigned width, height, terrain, red, green, blue;
4846 unsigned char found, mask, cityorcounty;
4847 int indx, x, y, z=1, x0, y0, dBm, level, hundreds,
4848 tens, units, match, colorwidth;
4849 double conversion, one_over_gamma, lat, lon,
4850 north, south, east, west, minwest;
4853 one_over_gamma=1.0/GAMMA;
4854 conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
4856 width=(unsigned)(ippd*ReduceAngle(max_west-min_west));
4857 height=(unsigned)(ippd*ReduceAngle(max_north-min_north));
4859 LoadDBMColors(xmtr[0]);
4863 strncpy(filename, xmtr[0].filename,254);
4864 filename[strlen(filename)-4]=0; /* Remove .qth */
4871 if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.')
4877 mapfile[x]=filename[x];
4878 geofile[x]=filename[x];
4879 kmlfile[x]=filename[x];
4898 minwest=((double)min_west)+dpp;
4903 north=(double)max_north-dpp;
4906 south=(double)min_north; /* No bottom legend */
4908 south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */
4910 east=(minwest<180.0?-minwest:360.0-min_west);
4911 west=(double)(max_west<180?-max_west:360-max_west);
4915 fd=fopen(geofile,"wb");
4917 fprintf(fd,"FILENAME\t%s\n",mapfile);
4918 fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
4919 fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north);
4921 fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south);
4922 fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height);
4924 fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version);
4931 fd=fopen(kmlfile,"wb");
4933 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
4934 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
4935 fprintf(fd,"<!-- Generated by %s Version %s -->\n",splat_name,splat_version);
4936 fprintf(fd," <Folder>\n");
4937 fprintf(fd," <name>%s</name>\n",splat_name);
4938 fprintf(fd," <description>%s Transmitter Coverage Overlay</description>\n",xmtr[0].name);
4939 fprintf(fd," <GroundOverlay>\n");
4940 fprintf(fd," <name>SPLAT! Signal Power Level Overlay</name>\n");
4941 fprintf(fd," <description>SPLAT! Coverage</description>\n");
4942 fprintf(fd," <Icon>\n");
4943 fprintf(fd," <href>%s</href>\n",mapfile);
4944 fprintf(fd," </Icon>\n");
4945 fprintf(fd," <opacity>128</opacity>\n");
4946 fprintf(fd," <LatLonBox>\n");
4947 fprintf(fd," <north>%.5f</north>\n",north);
4948 fprintf(fd," <south>%.5f</south>\n",south);
4949 fprintf(fd," <east>%.5f</east>\n",east);
4950 fprintf(fd," <west>%.5f</west>\n",west);
4951 fprintf(fd," <rotation>0.0</rotation>\n");
4952 fprintf(fd," </LatLonBox>\n");
4953 fprintf(fd," </GroundOverlay>\n");
4955 for (x=0; x<txsites; x++)
4957 fprintf(fd," <Placemark>\n");
4958 fprintf(fd," <name>%s</name>\n",xmtr[x].name);
4959 fprintf(fd," <visibility>1</visibility>\n");
4960 fprintf(fd," <Style>\n");
4961 fprintf(fd," <IconStyle>\n");
4962 fprintf(fd," <Icon>\n");
4963 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
4964 fprintf(fd," <x>224</x>\n");
4965 fprintf(fd," <y>224</y>\n");
4966 fprintf(fd," <w>32</w>\n");
4967 fprintf(fd," <h>32</h>\n");
4968 fprintf(fd," </Icon>\n");
4969 fprintf(fd," </IconStyle>\n");
4970 fprintf(fd," </Style>\n");
4971 fprintf(fd," <Point>\n");
4972 fprintf(fd," <extrude>1</extrude>\n");
4973 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
4974 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);
4975 fprintf(fd," </Point>\n");
4976 fprintf(fd," </Placemark>\n");
4979 fprintf(fd," </Folder>\n");
4980 fprintf(fd,"</kml>\n");
4985 fd=fopen(mapfile,"wb");
4987 fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
4988 fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
4991 for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y))
4993 for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x))
4998 for (indx=0, found=0; indx<MAXPAGES && found==0;)
5000 x0=(int)rint(ppd*(lat-(double)dem[indx].min_north));
5001 y0=mpi-(int)rint(ppd*(LonDiff((double)dem[indx].max_west,lon)));
5003 if (x0>=0 && x0<=mpi && y0>=0 && y0<=mpi)
5011 mask=dem[indx].mask[x0][y0];
5012 dBm=(dem[indx].signal[x0][y0])-200;
5021 if (dBm>=region.level[0])
5025 for (z=1; (z<region.levels && match==255); z++)
5027 if (dBm<region.level[z-1] && dBm>=region.level[z])
5032 if (match<region.levels)
5034 red=region.color[match][0];
5035 green=region.color[match][1];
5036 blue=region.color[match][2];
5041 /* Text Labels: Red or otherwise */
5043 if (red>=180 && green<=75 && blue<=75 && dBm!=0)
5044 fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
5046 fprintf(fd,"%c%c%c",255,0,0);
5053 /* County Boundaries: Black */
5055 fprintf(fd,"%c%c%c",0,0,0);
5060 if (cityorcounty==0)
5062 if (contour_threshold!=0 && dBm<contour_threshold)
5064 if (ngs) /* No terrain */
5065 fprintf(fd,"%c%c%c",255,255,255);
5068 /* Display land or sea elevation */
5070 if (dem[indx].data[x0][y0]==0)
5071 fprintf(fd,"%c%c%c",0,0,170);
5074 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
5075 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
5082 /* Plot signal power level regions in color */
5084 if (red!=0 || green!=0 || blue!=0)
5085 fprintf(fd,"%c%c%c",red,green,blue);
5087 else /* terrain / sea-level */
5090 fprintf(fd,"%c%c%c",255,255,255);
5093 if (dem[indx].data[x0][y0]==0)
5094 fprintf(fd,"%c%c%c",0,0,170);
5097 /* Elevation: Greyscale */
5098 terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
5099 fprintf(fd,"%c%c%c",terrain,terrain,terrain);
5109 /* We should never get here, but if */
5110 /* we do, display the region as black */
5112 fprintf(fd,"%c%c%c",0,0,0);
5117 if (kml==0 && geo==0)
5119 /* Display legend along bottom of image
5120 if not generating .kml or .geo output. */
5122 colorwidth=(int)rint((float)width/(float)region.levels);
5124 for (y0=0; y0<30; y0++)
5126 for (x0=0; x0<(int)width; x0++)
5131 level=abs(region.level[indx]);
5136 level-=(hundreds*100);
5145 if (y0>=8 && y0<=23)
5149 if (region.level[indx]<0)
5152 if (fontdata[16*('-')+(y0-8)]&(128>>(x-5)))
5159 if (fontdata[16*('+')+(y0-8)]&(128>>(x-5)))
5164 if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-13)))
5168 if (tens>0 || hundreds>0)
5172 if (region.level[indx]<0)
5175 if (fontdata[16*('-')+(y0-8)]&(128>>(x-13)))
5182 if (fontdata[16*('+')+(y0-8)]&(128>>(x-13)))
5188 if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-21)))
5192 if (hundreds==0 && tens==0)
5194 if (region.level[indx]<0)
5197 if (fontdata[16*('-')+(y0-8)]&(128>>(x-21)))
5204 if (fontdata[16*('+')+(y0-8)]&(128>>(x-21)))
5210 if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-29)))
5214 if (fontdata[16*('d')+(y0-8)]&(128>>(x-37)))
5218 if (fontdata[16*('B')+(y0-8)]&(128>>(x-45)))
5222 if (fontdata[16*('m')+(y0-8)]&(128>>(x-53)))
5226 if (indx>region.levels)
5227 fprintf(fd,"%c%c%c",0,0,0);
5230 red=region.color[indx][0];
5231 green=region.color[indx][1];
5232 blue=region.color[indx][2];
5234 fprintf(fd,"%c%c%c",red,green,blue);
5241 fprintf(stdout,"Done!\n");
5245 void GraphTerrain(struct site source, struct site destination, char *name)
5247 /* This function invokes gnuplot to generate an appropriate
5248 output file indicating the terrain profile between the source
5249 and destination locations when the -p command line option
5250 is used. "basename" is the name assigned to the output
5251 file generated by gnuplot. The filename extension is used
5252 to set gnuplot's terminal setting and output file type.
5253 If no extension is found, .png is assumed. */
5256 char basename[255], term[30], ext[15];
5257 double minheight=100000.0, maxheight=-100000.0;
5258 FILE *fd=NULL, *fd1=NULL;
5260 ReadPath(destination,source);
5262 fd=fopen("profile.gp","wb");
5265 fd1=fopen("clutter.gp","wb");
5267 for (x=0; x<path.length; x++)
5269 if ((path.elevation[x]+clutter)>maxheight)
5270 maxheight=path.elevation[x]+clutter;
5272 if (path.elevation[x]<minheight)
5273 minheight=path.elevation[x];
5277 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*path.elevation[x]);
5279 if (fd1!=NULL && x>0 && x<path.length-2)
5280 fprintf(fd1,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*(path.elevation[x]==0.0?path.elevation[x]:(path.elevation[x]+clutter)));
5285 fprintf(fd,"%f\t%f\n",path.distance[x],path.elevation[x]);
5287 if (fd1!=NULL && x>0 && x<path.length-2)
5288 fprintf(fd1,"%f\t%f\n",path.distance[x],(path.elevation[x]==0.0?path.elevation[x]:(path.elevation[x]+clutter)));
5299 /* Default filename and output file type */
5301 strncpy(basename,"profile\0",8);
5302 strncpy(term,"png\0",4);
5303 strncpy(ext,"png\0",4);
5308 /* Extract extension and terminal type from "name" */
5312 strncpy(basename,name,254);
5314 for (x=y-1; x>0 && name[x]!='.'; x--);
5316 if (x>0) /* Extension found */
5318 for (z=x+1; z<=y && (z-(x+1))<10; z++)
5320 ext[z-(x+1)]=tolower(name[z]);
5321 term[z-(x+1)]=name[z];
5324 ext[z-(x+1)]=0; /* Ensure an ending 0 */
5329 if (ext[0]==0) /* No extension -- Default is png */
5331 strncpy(term,"png\0",4);
5332 strncpy(ext,"png\0",4);
5336 /* Either .ps or .postscript may be used
5337 as an extension for postscript output. */
5339 if (strncmp(term,"postscript",10)==0)
5340 strncpy(ext,"ps\0",3);
5342 else if (strncmp(ext,"ps",2)==0)
5343 strncpy(term,"postscript enhanced color\0",26);
5345 minheight-=(0.01*maxheight);
5347 fd=fopen("splat.gp","w");
5348 fprintf(fd,"set grid\n");
5349 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", metric?minheight*METERS_PER_FOOT:minheight, metric?maxheight*METERS_PER_FOOT:maxheight);
5350 fprintf(fd,"set encoding iso_8859_1\n");
5351 fprintf(fd,"set term %s\n",term);
5352 fprintf(fd,"set title \"%s Terrain Profile Between %s and %s (%.2f%c Azimuth)\"\n",splat_name,destination.name, source.name, Azimuth(destination,source),176);
5356 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination));
5357 fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (meters)\"\n");
5362 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination));
5363 fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (feet)\"\n");
5366 fprintf(fd,"set output \"%s.%s\"\n",basename,ext);
5371 fprintf(fd,"plot \"profile.gp\" title \"Terrain Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f meters)\" with lines\n",clutter*METERS_PER_FOOT);
5373 fprintf(fd,"plot \"profile.gp\" title \"Terrain Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f feet)\" with lines\n",clutter);
5377 fprintf(fd,"plot \"profile.gp\" title \"\" with lines\n");
5381 x=system("gnuplot splat.gp");
5388 unlink("profile.gp");
5391 fprintf(stdout,"Terrain plot written to: \"%s.%s\"\n",basename,ext);
5396 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
5399 void GraphElevation(struct site source, struct site destination, char *name)
5401 /* This function invokes gnuplot to generate an appropriate
5402 output file indicating the terrain elevation profile between
5403 the source and destination locations when the -e command line
5404 option is used. "basename" is the name assigned to the output
5405 file generated by gnuplot. The filename extension is used
5406 to set gnuplot's terminal setting and output file type.
5407 If no extension is found, .png is assumed. */
5410 char basename[255], term[30], ext[15];
5411 double angle, clutter_angle=0.0, refangle, maxangle=-90.0,
5412 minangle=90.0, distance;
5413 struct site remote, remote2;
5414 FILE *fd=NULL, *fd1=NULL, *fd2=NULL;
5416 ReadPath(destination,source); /* destination=RX, source=TX */
5417 refangle=ElevationAngle(destination,source);
5418 distance=Distance(source,destination);
5420 fd=fopen("profile.gp","wb");
5423 fd1=fopen("clutter.gp","wb");
5425 fd2=fopen("reference.gp","wb");
5427 for (x=1; x<path.length-1; x++)
5429 remote.lat=path.lat[x];
5430 remote.lon=path.lon[x];
5432 angle=ElevationAngle(destination,remote);
5436 remote2.lat=path.lat[x];
5437 remote2.lon=path.lon[x];
5439 if (path.elevation[x]!=0.0)
5440 remote2.alt=clutter;
5444 clutter_angle=ElevationAngle(destination,remote2);
5449 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],angle);
5452 fprintf(fd1,"%f\t%f\n",KM_PER_MILE*path.distance[x],clutter_angle);
5454 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[x],refangle);
5459 fprintf(fd,"%f\t%f\n",path.distance[x],angle);
5462 fprintf(fd1,"%f\t%f\n",path.distance[x],clutter_angle);
5464 fprintf(fd2,"%f\t%f\n",path.distance[x],refangle);
5470 if (clutter_angle>maxangle)
5471 maxangle=clutter_angle;
5479 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],refangle);
5480 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],refangle);
5485 fprintf(fd,"%f\t%f\n",path.distance[path.length-1],refangle);
5486 fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],refangle);
5498 /* Default filename and output file type */
5500 strncpy(basename,"profile\0",8);
5501 strncpy(term,"png\0",4);
5502 strncpy(ext,"png\0",4);
5507 /* Extract extension and terminal type from "name" */
5511 strncpy(basename,name,254);
5513 for (x=y-1; x>0 && name[x]!='.'; x--);
5515 if (x>0) /* Extension found */
5517 for (z=x+1; z<=y && (z-(x+1))<10; z++)
5519 ext[z-(x+1)]=tolower(name[z]);
5520 term[z-(x+1)]=name[z];
5523 ext[z-(x+1)]=0; /* Ensure an ending 0 */
5528 if (ext[0]==0) /* No extension -- Default is png */
5530 strncpy(term,"png\0",4);
5531 strncpy(ext,"png\0",4);
5535 /* Either .ps or .postscript may be used
5536 as an extension for postscript output. */
5538 if (strncmp(term,"postscript",10)==0)
5539 strncpy(ext,"ps\0",3);
5541 else if (strncmp(ext,"ps",2)==0)
5542 strncpy(term,"postscript enhanced color\0",26);
5544 fd=fopen("splat.gp","w");
5546 fprintf(fd,"set grid\n");
5549 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", (-fabs(refangle)-0.25), maxangle+0.25);
5551 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minangle, refangle+(-minangle/8.0));
5553 fprintf(fd,"set encoding iso_8859_1\n");
5554 fprintf(fd,"set term %s\n",term);
5555 fprintf(fd,"set title \"%s Elevation Profile Between %s and %s (%.2f%c azimuth)\"\n",splat_name,destination.name,source.name,Azimuth(destination,source),176);
5558 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*distance);
5560 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,distance);
5563 fprintf(fd,"set ylabel \"Elevation Angle Along LOS Path Between\\n%s and %s (degrees)\"\n",destination.name,source.name);
5564 fprintf(fd,"set output \"%s.%s\"\n",basename,ext);
5569 fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f meters)\" with lines, \"reference.gp\" title \"Line of Sight Path (%.2f%c elevation)\" with lines\n",clutter*METERS_PER_FOOT,refangle,176);
5571 fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f feet)\" with lines, \"reference.gp\" title \"Line of Sight Path (%.2f%c elevation)\" with lines\n",clutter,refangle,176);
5575 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);
5579 x=system("gnuplot splat.gp");
5586 unlink("profile.gp");
5587 unlink("reference.gp");
5590 unlink("clutter.gp");
5593 fprintf(stdout,"Elevation plot written to: \"%s.%s\"\n",basename,ext);
5598 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
5601 void GraphHeight(struct site source, struct site destination, char *name, unsigned char fresnel_plot, unsigned char normalized)
5603 /* This function invokes gnuplot to generate an appropriate
5604 output file indicating the terrain height profile between
5605 the source and destination locations referenced to the
5606 line-of-sight path between the receive and transmit sites
5607 when the -h or -H command line option is used. "basename"
5608 is the name assigned to the output file generated by gnuplot.
5609 The filename extension is used to set gnuplot's terminal
5610 setting and output file type. If no extension is found,
5614 char basename[255], term[30], ext[15];
5615 double a, b, c, height=0.0, refangle, cangle, maxheight=-100000.0,
5616 minheight=100000.0, lambda=0.0, f_zone=0.0, fpt6_zone=0.0,
5617 nm=0.0, nb=0.0, ed=0.0, es=0.0, r=0.0, d=0.0, d1=0.0,
5618 terrain, azimuth, distance, dheight=0.0, minterrain=100000.0,
5619 minearth=100000.0, miny, maxy, min2y, max2y;
5621 FILE *fd=NULL, *fd1=NULL, *fd2=NULL, *fd3=NULL, *fd4=NULL, *fd5=NULL;
5623 ReadPath(destination,source); /* destination=RX, source=TX */
5624 azimuth=Azimuth(destination,source);
5625 distance=Distance(destination,source);
5626 refangle=ElevationAngle(destination,source);
5627 b=GetElevation(destination)+destination.alt+earthradius;
5629 /* Wavelength and path distance (great circle) in feet. */
5633 lambda=9.8425e8/(LR.frq_mhz*1e6);
5634 d=5280.0*path.distance[path.length-1];
5639 ed=GetElevation(destination);
5640 es=GetElevation(source);
5641 nb=-destination.alt-ed;
5642 nm=(-source.alt-es-nb)/(path.distance[path.length-1]);
5645 fd=fopen("profile.gp","wb");
5648 fd1=fopen("clutter.gp","wb");
5650 fd2=fopen("reference.gp","wb");
5651 fd5=fopen("curvature.gp", "wb");
5653 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5655 fd3=fopen("fresnel.gp", "wb");
5656 fd4=fopen("fresnel_pt_6.gp", "wb");
5659 for (x=0; x<path.length-1; x++)
5661 remote.lat=path.lat[x];
5662 remote.lon=path.lon[x];
5665 terrain=GetElevation(remote);
5668 terrain+=destination.alt; /* RX antenna spike */
5670 a=terrain+earthradius;
5671 cangle=5280.0*Distance(destination,remote)/earthradius;
5672 c=b*sin(refangle*DEG2RAD+HALFPI)/sin(HALFPI-refangle*DEG2RAD-cangle);
5676 /* Per Fink and Christiansen, Electronics
5677 * Engineers' Handbook, 1989:
5679 * H = sqrt(lamba * d1 * (d - d1)/d)
5681 * where H is the distance from the LOS
5682 * path to the first Fresnel zone boundary.
5685 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5687 d1=5280.0*path.distance[x];
5688 f_zone=-1.0*sqrt(lambda*d1*(d-d1)/d);
5689 fpt6_zone=f_zone*fzone_clearance;
5694 r=-(nm*path.distance[x])-nb;
5697 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5709 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*height);
5711 if (fd1!=NULL && x>0 && x<path.length-2)
5712 fprintf(fd1,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*(terrain==0.0?height:(height+clutter)));
5714 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*r);
5715 fprintf(fd5,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*(height-terrain));
5720 fprintf(fd,"%f\t%f\n",path.distance[x],height);
5722 if (fd1!=NULL && x>0 && x<path.length-2)
5723 fprintf(fd1,"%f\t%f\n",path.distance[x],(terrain==0.0?height:(height+clutter)));
5725 fprintf(fd2,"%f\t%f\n",path.distance[x],r);
5726 fprintf(fd5,"%f\t%f\n",path.distance[x],height-terrain);
5729 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5733 fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*f_zone);
5734 fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*fpt6_zone);
5739 fprintf(fd3,"%f\t%f\n",path.distance[x],f_zone);
5740 fprintf(fd4,"%f\t%f\n",path.distance[x],fpt6_zone);
5743 if (f_zone<minheight)
5747 if ((height+clutter)>maxheight)
5748 maxheight=height+clutter;
5750 if (height<minheight)
5756 if (terrain<minterrain)
5759 if ((height-terrain)<minearth)
5760 minearth=height-terrain;
5764 r=-(nm*path.distance[path.length-1])-nb;
5770 fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
5771 fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
5776 fprintf(fd,"%f\t%f\n",path.distance[path.length-1],r);
5777 fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],r);
5780 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5784 fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
5785 fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
5790 fprintf(fd3,"%f\t%f\n",path.distance[path.length-1],r);
5791 fprintf(fd4,"%f\t%f\n",path.distance[path.length-1],r);
5809 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5817 /* Default filename and output file type */
5819 strncpy(basename,"profile\0",8);
5820 strncpy(term,"png\0",4);
5821 strncpy(ext,"png\0",4);
5826 /* Extract extension and terminal type from "name" */
5830 strncpy(basename,name,254);
5832 for (x=y-1; x>0 && name[x]!='.'; x--);
5834 if (x>0) /* Extension found */
5836 for (z=x+1; z<=y && (z-(x+1))<10; z++)
5838 ext[z-(x+1)]=tolower(name[z]);
5839 term[z-(x+1)]=name[z];
5842 ext[z-(x+1)]=0; /* Ensure an ending 0 */
5847 if (ext[0]==0) /* No extension -- Default is png */
5849 strncpy(term,"png\0",4);
5850 strncpy(ext,"png\0",4);
5854 /* Either .ps or .postscript may be used
5855 as an extension for postscript output. */
5857 if (strncmp(term,"postscript",10)==0)
5858 strncpy(ext,"ps\0",3);
5860 else if (strncmp(ext,"ps",2)==0)
5861 strncpy(term,"postscript enhanced color\0",26);
5863 fd=fopen("splat.gp","w");
5865 dheight=maxheight-minheight;
5866 miny=minheight-0.15*dheight;
5867 maxy=maxheight+0.05*dheight;
5872 dheight=maxheight-minheight;
5873 min2y=miny-minterrain+0.05*dheight;
5877 miny-=min2y-minearth+0.05*dheight;
5878 min2y=minearth-0.05*dheight;
5881 max2y=min2y+maxy-miny;
5883 fprintf(fd,"set grid\n");
5884 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", metric?miny*METERS_PER_FOOT:miny, metric?maxy*METERS_PER_FOOT:maxy);
5885 fprintf(fd,"set y2range [%2.3f to %2.3f]\n", metric?min2y*METERS_PER_FOOT:min2y, metric?max2y*METERS_PER_FOOT:max2y);
5886 fprintf(fd,"set xrange [-0.5 to %2.3f]\n",metric?KM_PER_MILE*rint(distance+0.5):rint(distance+0.5));
5887 fprintf(fd,"set encoding iso_8859_1\n");
5888 fprintf(fd,"set term %s\n",term);
5890 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5891 fprintf(fd,"set title \"%s Path Profile Between %s and %s (%.2f%c azimuth)\\nWith First Fresnel Zone\"\n",splat_name, destination.name, source.name, azimuth,176);
5894 fprintf(fd,"set title \"%s Height Profile Between %s and %s (%.2f%c azimuth)\"\n",splat_name, destination.name, source.name, azimuth,176);
5897 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination));
5899 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination));
5904 fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (meters)\"\n",destination.name,source.name);
5907 fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (feet)\"\n",destination.name,source.name);
5914 fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between\\n%s and %s (meters)\"\n",destination.name,source.name);
5917 fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between\\n%s and %s (feet)\"\n",destination.name,source.name);
5920 fprintf(fd,"set output \"%s.%s\"\n",basename,ext);
5922 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5927 fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f meters)\" 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",clutter*METERS_PER_FOOT,LR.frq_mhz,fzone_clearance*100.0);
5929 fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f feet)\" 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",clutter,LR.frq_mhz,fzone_clearance*100.0);
5933 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",LR.frq_mhz,fzone_clearance*100.0);
5941 fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f meters)\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines\n",clutter*METERS_PER_FOOT);
5943 fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f feet)\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines\n",clutter);
5947 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");
5953 x=system("gnuplot splat.gp");
5960 unlink("profile.gp");
5961 unlink("reference.gp");
5962 unlink("curvature.gp");
5965 unlink("clutter.gp");
5967 if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
5969 unlink("fresnel.gp");
5970 unlink("fresnel_pt_6.gp");
5974 fprintf(stdout,"\nHeight plot written to: \"%s.%s\"",basename,ext);
5979 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
5982 void ObstructionAnalysis(struct site xmtr, struct site rcvr, double f, FILE *outfile)
5984 /* Perform an obstruction analysis along the
5985 path between receiver and transmitter. */
5989 double h_r, h_t, h_x, h_r_orig, cos_tx_angle, cos_test_angle,
5990 cos_tx_angle_f1, cos_tx_angle_fpt6, d_tx, d_x,
5991 h_r_f1, h_r_fpt6, h_f, h_los, lambda=0.0;
5992 char string[255], string_fpt6[255], string_f1[255];
5994 ReadPath(xmtr,rcvr);
5995 h_r=GetElevation(rcvr)+rcvr.alt+earthradius;
5999 h_t=GetElevation(xmtr)+xmtr.alt+earthradius;
6000 d_tx=5280.0*Distance(rcvr,xmtr);
6001 cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx);
6002 cos_tx_angle_f1=cos_tx_angle;
6003 cos_tx_angle_fpt6=cos_tx_angle;
6006 lambda=9.8425e8/(f*1e6);
6010 fprintf(outfile,"Terrain has been raised by");
6013 fprintf(outfile," %.2f meters",METERS_PER_FOOT*clutter);
6015 fprintf(outfile," %.2f feet",clutter);
6017 fprintf(outfile," to account for ground clutter.\n\n");
6020 /* At each point along the path calculate the cosine
6021 of a sort of "inverse elevation angle" at the receiver.
6022 From the antenna, 0 deg. looks at the ground, and 90 deg.
6023 is parallel to the ground.
6025 Start at the receiver. If this is the lowest antenna,
6026 then terrain obstructions will be nearest to it. (Plus,
6027 that's the way SPLAT!'s original los() did it.)
6029 Calculate cosines only. That's sufficient to compare
6030 angles and it saves the extra computational burden of
6031 acos(). However, note the inverted comparison: if
6032 acos(A) > acos(B), then B > A. */
6034 for (x=path.length-1; x>0; x--)
6036 site_x.lat=path.lat[x];
6037 site_x.lon=path.lon[x];
6040 h_x=GetElevation(site_x)+earthradius+clutter;
6041 d_x=5280.0*Distance(rcvr,site_x);
6043 /* Deal with the LOS path first. */
6045 cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x);
6047 if (cos_tx_angle>cos_test_angle)
6050 fprintf(outfile,"Between %s and %s, %s detected obstructions at:\n\n",rcvr.name,xmtr.name,splat_name);
6052 if (site_x.lat>=0.0)
6055 fprintf(outfile," %8.4f N,%9.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));
6057 fprintf(outfile," %8.4f N,%9.4f W, %5.2f miles, %6.2f feet AMSL\n",site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius);
6063 fprintf(outfile," %8.4f S,%9.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));
6066 fprintf(outfile," %8.4f S,%9.4f W, %5.2f miles, %6.2f feet AMSL\n",-site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius);
6070 while (cos_tx_angle>cos_test_angle)
6073 cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x);
6074 cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx);
6079 /* Now clear the first Fresnel zone... */
6081 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);
6082 h_los=sqrt(h_r_f1*h_r_f1+d_x*d_x-2*h_r_f1*d_x*cos_tx_angle_f1);
6083 h_f=h_los-sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
6088 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);
6089 h_los=sqrt(h_r_f1*h_r_f1+d_x*d_x-2*h_r_f1*d_x*cos_tx_angle_f1);
6090 h_f=h_los-sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
6093 /* and clear the 60% F1 zone. */
6095 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);
6096 h_los=sqrt(h_r_fpt6*h_r_fpt6+d_x*d_x-2*h_r_fpt6*d_x*cos_tx_angle_fpt6);
6097 h_f=h_los-fzone_clearance*sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
6102 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);
6103 h_los=sqrt(h_r_fpt6*h_r_fpt6+d_x*d_x-2*h_r_fpt6*d_x*cos_tx_angle_fpt6);
6104 h_f=h_los-fzone_clearance*sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
6112 snprintf(string,150,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear all obstructions detected by %s.\n",rcvr.name, METERS_PER_FOOT*(h_r-GetElevation(rcvr)-earthradius),splat_name);
6114 snprintf(string,150,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear all obstructions detected by %s.\n",rcvr.name, h_r-GetElevation(rcvr)-earthradius,splat_name);
6118 snprintf(string,150,"\nNo obstructions to LOS path due to terrain were detected by %s\n",splat_name);
6122 if (h_r_fpt6>h_r_orig)
6125 snprintf(string_fpt6,150,"\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);
6128 snprintf(string_fpt6,150,"\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);
6132 snprintf(string_fpt6,150,"\n%.0f%c of the first Fresnel zone is clear.\n",fzone_clearance*100.0,37);
6134 if (h_r_f1>h_r_orig)
6137 snprintf(string_f1,150,"\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));
6140 snprintf(string_f1,150,"\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);
6145 snprintf(string_f1,150,"\nThe first Fresnel zone is clear.\n");
6148 fprintf(outfile,"%s",string);
6152 fprintf(outfile,"%s",string_f1);
6153 fprintf(outfile,"%s",string_fpt6);
6157 void PathReport(struct site source, struct site destination, char *name, char graph_it)
6159 /* This function writes a SPLAT! Path Report (name.txt) to
6160 the filesystem. If (graph_it == 1), then gnuplot is invoked
6161 to generate an appropriate output file indicating the Longley-Rice
6162 model loss between the source and destination locations.
6163 "filename" is the name assigned to the output file generated
6164 by gnuplot. The filename extension is used to set gnuplot's
6165 terminal setting and output file type. If no extension is
6166 found, .png is assumed. */
6168 int x, y, z, errnum;
6169 char basename[255], term[30], ext[15], strmode[100],
6170 report_name[80], block=0;
6171 double maxloss=-100000.0, minloss=100000.0, loss, haavt,
6172 angle1, angle2, azimuth, pattern=1.0, patterndB=0.0,
6173 total_loss=0.0, cos_xmtr_angle, cos_test_angle=0.0,
6174 source_alt, test_alt, dest_alt, source_alt2, dest_alt2,
6175 distance, elevation, four_thirds_earth, field_strength,
6176 free_space_loss=0.0, eirp=0.0, voltage, rxp, dBm,
6178 FILE *fd=NULL, *fd2=NULL;
6180 sprintf(report_name,"%s-to-%s.txt",source.name,destination.name);
6182 four_thirds_earth=FOUR_THIRDS*EARTHRADIUS;
6184 for (x=0; report_name[x]!=0; x++)
6185 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
6188 fd2=fopen(report_name,"w");
6190 fprintf(fd2,"\n\t\t--==[ %s v%s Path Analysis ]==--\n\n",splat_name,splat_version);
6191 fprintf(fd2,"%s\n\n",dashes);
6192 fprintf(fd2,"Transmitter site: %s\n",source.name);
6194 if (source.lat>=0.0)
6196 fprintf(fd2,"Site location: %.4f North / %.4f West",source.lat, source.lon);
6197 fprintf(fd2, " (%s N / ", dec2dms(source.lat));
6203 fprintf(fd2,"Site location: %.4f South / %.4f West",-source.lat, source.lon);
6204 fprintf(fd2, " (%s S / ", dec2dms(source.lat));
6207 fprintf(fd2, "%s W)\n", dec2dms(source.lon));
6211 fprintf(fd2,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(source));
6212 fprintf(fd2,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*source.alt,METERS_PER_FOOT*(source.alt+GetElevation(source)));
6217 fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(source));
6218 fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",source.alt, source.alt+GetElevation(source));
6226 fprintf(fd2,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt);
6228 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
6231 azimuth=Azimuth(source,destination);
6232 angle1=ElevationAngle(source,destination);
6233 angle2=ElevationAngle2(source,destination,earthradius);
6235 if (got_azimuth_pattern || got_elevation_pattern)
6237 x=(int)rint(10.0*(10.0-angle2));
6239 if (x>=0 && x<=1000)
6240 pattern=(double)LR.antenna_pattern[(int)rint(azimuth)][x];
6242 patterndB=20.0*log10(pattern);
6246 fprintf(fd2,"Distance to %s: %.2f kilometers\n",destination.name,KM_PER_MILE*Distance(source,destination));
6249 fprintf(fd2,"Distance to %s: %.2f miles\n",destination.name,Distance(source,destination));
6251 fprintf(fd2,"Azimuth to %s: %.2f degrees\n",destination.name,azimuth);
6254 fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",destination.name,angle1);
6257 fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",destination.name,angle1);
6259 if ((angle2-angle1)>0.0001)
6262 fprintf(fd2,"Depression");
6264 fprintf(fd2,"Elevation");
6266 fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2);
6269 fprintf(fd2,"\n%s\n\n",dashes);
6273 fprintf(fd2,"Receiver site: %s\n",destination.name);
6275 if (destination.lat>=0.0)
6277 fprintf(fd2,"Site location: %.4f North / %.4f West",destination.lat, destination.lon);
6278 fprintf(fd2, " (%s N / ", dec2dms(destination.lat));
6283 fprintf(fd2,"Site location: %.4f South / %.4f West",-destination.lat, destination.lon);
6284 fprintf(fd2, " (%s S / ", dec2dms(destination.lat));
6287 fprintf(fd2, "%s W)\n", dec2dms(destination.lon));
6291 fprintf(fd2,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(destination));
6292 fprintf(fd2,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*destination.alt, METERS_PER_FOOT*(destination.alt+GetElevation(destination)));
6297 fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(destination));
6298 fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",destination.alt, destination.alt+GetElevation(destination));
6301 haavt=haat(destination);
6306 fprintf(fd2,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt);
6308 fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt);
6312 fprintf(fd2,"Distance to %s: %.2f kilometers\n",source.name,KM_PER_MILE*Distance(source,destination));
6315 fprintf(fd2,"Distance to %s: %.2f miles\n",source.name,Distance(source,destination));
6317 azimuth=Azimuth(destination,source);
6319 angle1=ElevationAngle(destination,source);
6320 angle2=ElevationAngle2(destination,source,earthradius);
6322 fprintf(fd2,"Azimuth to %s: %.2f degrees\n",source.name,azimuth);
6325 fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",source.name,angle1);
6328 fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",source.name,angle1);
6330 if ((angle2-angle1)>0.0001)
6333 fprintf(fd2,"Depression");
6335 fprintf(fd2,"Elevation");
6337 fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2);
6340 fprintf(fd2,"\n%s\n\n",dashes);
6344 fprintf(fd2,"Longley-Rice path calculation parameters used in this analysis:\n\n");
6345 fprintf(fd2,"Earth's Dielectric Constant: %.3lf\n",LR.eps_dielect);
6346 fprintf(fd2,"Earth's Conductivity: %.3lf Siemens/meter\n",LR.sgm_conductivity);
6347 fprintf(fd2,"Atmospheric Bending Constant (N-units): %.3lf ppm\n",LR.eno_ns_surfref);
6348 fprintf(fd2,"Frequency: %.3lf MHz\n",LR.frq_mhz);
6349 fprintf(fd2,"Radio Climate: %d (",LR.radio_climate);
6351 switch (LR.radio_climate)
6354 fprintf(fd2,"Equatorial");
6358 fprintf(fd2,"Continental Subtropical");
6362 fprintf(fd2,"Maritime Subtropical");
6366 fprintf(fd2,"Desert");
6370 fprintf(fd2,"Continental Temperate");
6374 fprintf(fd2,"Martitime Temperate, Over Land");
6378 fprintf(fd2,"Maritime Temperate, Over Sea");
6382 fprintf(fd2,"Unknown");
6385 fprintf(fd2,")\nPolarization: %d (",LR.pol);
6388 fprintf(fd2,"Horizontal");
6391 fprintf(fd2,"Vertical");
6393 fprintf(fd2,")\nFraction of Situations: %.1lf%c\n",LR.conf*100.0,37);
6394 fprintf(fd2,"Fraction of Time: %.1lf%c\n",LR.rel*100.0,37);
6398 fprintf(fd2,"Transmitter ERP: ");
6401 fprintf(fd2,"%.1lf milliwatts",1000.0*LR.erp);
6403 if (LR.erp>=1.0 && LR.erp<10.0)
6404 fprintf(fd2,"%.1lf Watts",LR.erp);
6406 if (LR.erp>=10.0 && LR.erp<10.0e3)
6407 fprintf(fd2,"%.0lf Watts",LR.erp);
6410 fprintf(fd2,"%.3lf kilowatts",LR.erp/1.0e3);
6412 dBm=10.0*(log10(LR.erp*1000.0));
6413 fprintf(fd2," (%+.2f dBm)\n",dBm);
6415 /* EIRP = ERP + 2.14 dB */
6417 fprintf(fd2,"Transmitter EIRP: ");
6419 eirp=LR.erp*1.636816521;
6422 fprintf(fd2,"%.1lf milliwatts",1000.0*eirp);
6424 if (eirp>=1.0 && eirp<10.0)
6425 fprintf(fd2,"%.1lf Watts",eirp);
6427 if (eirp>=10.0 && eirp<10.0e3)
6428 fprintf(fd2,"%.0lf Watts",eirp);
6431 fprintf(fd2,"%.3lf kilowatts",eirp/1.0e3);
6433 dBm=10.0*(log10(eirp*1000.0));
6434 fprintf(fd2," (%+.2f dBm)\n",dBm);
6437 fprintf(fd2,"\n%s\n\n",dashes);
6439 fprintf(fd2,"Summary for the link between %s and %s:\n\n",source.name, destination.name);
6442 fprintf(fd2,"%s antenna pattern towards %s: %.3f (%.2f dB)\n", source.name, destination.name, pattern, patterndB);
6444 ReadPath(source, destination); /* source=TX, destination=RX */
6446 /* Copy elevations plus clutter along
6447 path into the elev[] array. */
6449 for (x=1; x<path.length-1; x++)
6450 elev[x+2]=METERS_PER_FOOT*(path.elevation[x]==0.0?path.elevation[x]:(clutter+path.elevation[x]));
6452 /* Copy ending points without clutter */
6454 elev[2]=path.elevation[0]*METERS_PER_FOOT;
6455 elev[path.length+1]=path.elevation[path.length-1]*METERS_PER_FOOT;
6457 fd=fopen("profile.gp","w");
6459 azimuth=rint(Azimuth(source,destination));
6461 for (y=2; y<(path.length-1); y++) /* path.length-1 avoids LR error */
6463 distance=5280.0*path.distance[y];
6464 source_alt=four_thirds_earth+source.alt+path.elevation[0];
6465 dest_alt=four_thirds_earth+destination.alt+path.elevation[y];
6466 dest_alt2=dest_alt*dest_alt;
6467 source_alt2=source_alt*source_alt;
6469 /* Calculate the cosine of the elevation of
6470 the receiver as seen by the transmitter. */
6472 cos_xmtr_angle=((source_alt2)+(distance*distance)-(dest_alt2))/(2.0*source_alt*distance);
6474 if (got_elevation_pattern)
6476 /* If an antenna elevation pattern is available, the
6477 following code determines the elevation angle to
6478 the first obstruction along the path. */
6480 for (x=2, block=0; x<y && block==0; x++)
6482 distance=5280.0*(path.distance[y]-path.distance[x]);
6483 test_alt=four_thirds_earth+path.elevation[x];
6485 /* Calculate the cosine of the elevation
6486 angle of the terrain (test point)
6487 as seen by the transmitter. */
6489 cos_test_angle=((source_alt2)+(distance*distance)-(test_alt*test_alt))/(2.0*source_alt*distance);
6491 /* Compare these two angles to determine if
6492 an obstruction exists. Since we're comparing
6493 the cosines of these angles rather than
6494 the angles themselves, the sense of the
6495 following "if" statement is reversed from
6496 what it would be if the angles themselves
6499 if (cos_xmtr_angle>=cos_test_angle)
6503 /* At this point, we have the elevation angle
6504 to the first obstruction (if it exists). */
6507 /* Determine path loss for each point along the
6508 path using Longley-Rice's point_to_point mode
6509 starting at x=2 (number_of_points = 1), the
6510 shortest distance terrain can play a role in
6513 elev[0]=y-1; /* (number of points - 1) */
6515 /* Distance between elevation samples */
6517 elev[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]);
6519 point_to_point(elev, source.alt*METERS_PER_FOOT,
6520 destination.alt*METERS_PER_FOOT, LR.eps_dielect,
6521 LR.sgm_conductivity, LR.eno_ns_surfref, LR.frq_mhz,
6522 LR.radio_climate, LR.pol, LR.conf, LR.rel, loss,
6526 elevation=((acos(cos_test_angle))/DEG2RAD)-90.0;
6528 elevation=((acos(cos_xmtr_angle))/DEG2RAD)-90.0;
6530 /* Integrate the antenna's radiation
6531 pattern into the overall path loss. */
6533 x=(int)rint(10.0*(10.0-elevation));
6535 if (x>=0 && x<=1000)
6537 pattern=(double)LR.antenna_pattern[(int)azimuth][x];
6540 patterndB=20.0*log10(pattern);
6546 total_loss=loss-patterndB;
6549 fprintf(fd,"%f\t%f\n",KM_PER_MILE*(path.distance[path.length-1]-path.distance[y]),total_loss);
6552 fprintf(fd,"%f\t%f\n",path.distance[path.length-1]-path.distance[y],total_loss);
6554 if (total_loss>maxloss)
6557 if (total_loss<minloss)
6563 distance=Distance(source,destination);
6568 free_space_loss=36.6+(20.0*log10(LR.frq_mhz))+(20.0*log10(distance));
6570 fprintf(fd2,"Free space path loss: %.2f dB\n",free_space_loss);
6573 fprintf(fd2,"Longley-Rice path loss: %.2f dB\n",loss);
6575 if (free_space_loss!=0.0)
6576 fprintf(fd2,"Attenuation due to terrain shielding: %.2f dB\n",loss-free_space_loss);
6579 fprintf(fd2,"Total path loss including %s antenna pattern: %.2f dB\n",source.name,total_loss);
6583 field_strength=(139.4+(20.0*log10(LR.frq_mhz))-total_loss)+(10.0*log10(LR.erp/1000.0));
6585 /* dBm is referenced to EIRP */
6587 rxp=eirp/(pow(10.0,(total_loss/10.0)));
6588 dBm=10.0*(log10(rxp*1000.0));
6589 power_density=(eirp/(pow(10.0,(total_loss-free_space_loss)/10.0)));
6590 /* divide by 4*PI*distance_in_meters squared */
6591 power_density/=(4.0*PI*distance*distance*2589988.11);
6593 fprintf(fd2,"Field strength at %s: %.2f dBuV/meter\n", destination.name,field_strength);
6594 fprintf(fd2,"Signal power level at %s: %+.2f dBm\n",destination.name,dBm);
6595 fprintf(fd2,"Signal power density at %s: %+.2f dBW per square meter\n",destination.name,10.0*log10(power_density));
6596 voltage=1.0e6*sqrt(50.0*(eirp/(pow(10.0,(total_loss-2.14)/10.0))));
6597 fprintf(fd2,"Voltage across 50 ohm dipole at %s: %.2f uV (%.2f dBuV)\n",destination.name,voltage,20.0*log10(voltage));
6599 voltage=1.0e6*sqrt(75.0*(eirp/(pow(10.0,(total_loss-2.14)/10.0))));
6600 fprintf(fd2,"Voltage across 75 ohm dipole at %s: %.2f uV (%.2f dBuV)\n",destination.name,voltage,20.0*log10(voltage));
6603 fprintf(fd2,"Mode of propagation: %s\n",strmode);
6604 fprintf(fd2,"Longley-Rice model error number: %d",errnum);
6609 fprintf(fd2," (No error)\n");
6613 fprintf(fd2,"\n Warning: Some parameters are nearly out of range.\n");
6614 fprintf(fd2," Results should be used with caution.\n");
6618 fprintf(fd2,"\n Note: Default parameters have been substituted for impossible ones.\n");
6622 fprintf(fd2,"\n Warning: A combination of parameters is out of range.\n");
6623 fprintf(fd2," Results are probably invalid.\n");
6627 fprintf(fd2,"\n Warning: Some parameters are out of range.\n");
6628 fprintf(fd2," Results are probably invalid.\n");
6631 fprintf(fd2,"\n%s\n\n",dashes);
6634 fprintf(stdout,"\nPath Loss Report written to: \"%s\"\n",report_name);
6637 ObstructionAnalysis(source, destination, LR.frq_mhz, fd2);
6641 /* Skip plotting the graph if ONLY a path-loss report is needed. */
6647 /* Default filename and output file type */
6649 strncpy(basename,"profile\0",8);
6650 strncpy(term,"png\0",4);
6651 strncpy(ext,"png\0",4);
6656 /* Extract extension and terminal type from "name" */
6660 strncpy(basename,name,254);
6662 for (x=y-1; x>0 && name[x]!='.'; x--);
6664 if (x>0) /* Extension found */
6666 for (z=x+1; z<=y && (z-(x+1))<10; z++)
6668 ext[z-(x+1)]=tolower(name[z]);
6669 term[z-(x+1)]=name[z];
6672 ext[z-(x+1)]=0; /* Ensure an ending 0 */
6678 if (ext[0]==0) /* No extension -- Default is png */
6680 strncpy(term,"png\0",4);
6681 strncpy(ext,"png\0",4);
6684 /* Either .ps or .postscript may be used
6685 as an extension for postscript output. */
6687 if (strncmp(term,"postscript",10)==0)
6688 strncpy(ext,"ps\0",3);
6690 else if (strncmp(ext,"ps",2)==0)
6691 strncpy(term,"postscript enhanced color\0",26);
6693 fd=fopen("splat.gp","w");
6695 fprintf(fd,"set grid\n");
6696 fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minloss, maxloss);
6697 fprintf(fd,"set encoding iso_8859_1\n");
6698 fprintf(fd,"set term %s\n",term);
6699 fprintf(fd,"set title \"%s Loss Profile Along Path Between %s and %s (%.2f%c azimuth)\"\n",splat_name, destination.name, source.name, Azimuth(destination,source),176);
6702 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(destination,source));
6704 fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(destination,source));
6706 if (got_azimuth_pattern || got_elevation_pattern)
6707 fprintf(fd,"set ylabel \"Total Path Loss (including TX antenna pattern) (dB)");
6709 fprintf(fd,"set ylabel \"Longley-Rice Path Loss (dB)");
6711 fprintf(fd,"\"\nset output \"%s.%s\"\n",basename,ext);
6712 fprintf(fd,"plot \"profile.gp\" title \"Path Loss\" with lines\n");
6716 x=system("gnuplot splat.gp");
6723 unlink("profile.gp");
6724 unlink("reference.gp");
6727 fprintf(stdout,"Path loss plot written to: \"%s.%s\"\n",basename,ext);
6732 fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
6735 if (x!=-1 && gpsav==0)
6736 unlink("profile.gp");
6739 void SiteReport(struct site xmtr)
6741 char report_name[80];
6746 sprintf(report_name,"%s-site_report.txt",xmtr.name);
6748 for (x=0; report_name[x]!=0; x++)
6749 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
6752 fd=fopen(report_name,"w");
6754 fprintf(fd,"\n\t--==[ %s v%s Site Analysis Report For: %s ]==--\n\n",splat_name, splat_version, xmtr.name);
6756 fprintf(fd,"%s\n\n",dashes);
6760 fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon);
6761 fprintf(fd, " (%s N / ",dec2dms(xmtr.lat));
6766 fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon);
6767 fprintf(fd, " (%s S / ",dec2dms(xmtr.lat));
6770 fprintf(fd, "%s W)\n",dec2dms(xmtr.lon));
6774 fprintf(fd,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(xmtr));
6775 fprintf(fd,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*xmtr.alt, METERS_PER_FOOT*(xmtr.alt+GetElevation(xmtr)));
6780 fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr));
6781 fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr));
6786 if (terrain>-4999.0)
6789 fprintf(fd,"Antenna height above average terrain: %.2f meters\n\n",METERS_PER_FOOT*terrain);
6791 fprintf(fd,"Antenna height above average terrain: %.2f feet\n\n",terrain);
6793 /* Display the average terrain between 2 and 10 miles
6794 from the transmitter site at azimuths of 0, 45, 90,
6795 135, 180, 225, 270, and 315 degrees. */
6797 for (azi=0; azi<=315; azi+=45)
6799 fprintf(fd,"Average terrain at %3d degrees azimuth: ",azi);
6800 terrain=AverageTerrain(xmtr,(double)azi,2.0,10.0);
6802 if (terrain>-4999.0)
6805 fprintf(fd,"%.2f meters AMSL\n",METERS_PER_FOOT*terrain);
6807 fprintf(fd,"%.2f feet AMSL\n",terrain);
6811 fprintf(fd,"No terrain\n");
6815 fprintf(fd,"\n%s\n\n",dashes);
6817 fprintf(stdout,"\nSite analysis report written to: \"%s\"\n",report_name);
6820 void LoadTopoData(int max_lon, int min_lon, int max_lat, int min_lat)
6822 /* This function loads the SDF files required
6823 to cover the limits of the region specified. */
6825 int x, y, width, ymin, ymax;
6827 width=ReduceAngle(max_lon-min_lon);
6829 if ((max_lon-min_lon)<=180.0)
6831 for (y=0; y<=width; y++)
6832 for (x=min_lat; x<=max_lat; x++)
6834 ymin=(int)(min_lon+(double)y);
6851 snprintf(string,19,"%d:%d:%d:%d-hd",x, x+1, ymin, ymax);
6853 snprintf(string,16,"%d:%d:%d:%d",x, x+1, ymin, ymax);
6860 for (y=0; y<=width; y++)
6861 for (x=min_lat; x<=max_lat; x++)
6880 snprintf(string,19,"%d:%d:%d:%d-hd",x, x+1, ymin, ymax);
6882 snprintf(string,16,"%d:%d:%d:%d",x, x+1, ymin, ymax);
6888 int LoadANO(char *filename)
6890 /* This function reads a SPLAT! alphanumeric output
6891 file (-ani option) for analysis and/or map generation. */
6893 int error=0, max_west, min_west, max_north, min_north;
6894 char string[80], *pointer=NULL, *s=NULL;
6895 double latitude=0.0, longitude=0.0, azimuth=0.0, elevation=0.0,
6899 fd=fopen(filename,"r");
6903 s=fgets(string,78,fd);
6904 pointer=strchr(string,';');
6909 sscanf(string,"%d, %d",&max_west, &min_west);
6911 s=fgets(string,78,fd);
6912 pointer=strchr(string,';');
6917 sscanf(string,"%d, %d",&max_north, &min_north);
6919 s=fgets(string,78,fd);
6920 pointer=strchr(string,';');
6925 LoadTopoData(max_west-1, min_west, max_north-1, min_north);
6927 fprintf(stdout,"\nReading \"%s\"... ",filename);
6930 s=fgets(string,78,fd);
6931 sscanf(string,"%lf, %lf, %lf, %lf, %lf",&latitude, &longitude, &azimuth, &elevation, &ano);
6939 if (contour_threshold==0 || (fabs(ano)<=(double)contour_threshold))
6946 PutSignal(latitude,longitude,((unsigned char)round(ano)));
6950 if (LR.erp!=0.0 && dbm!=0)
6952 /* signal power level in dBm */
6954 if (contour_threshold==0 || (ano>=(double)contour_threshold))
6956 ano=200.0+rint(ano);
6964 PutSignal(latitude,longitude,((unsigned char)round(ano)));
6968 if (LR.erp!=0.0 && dbm==0)
6970 /* field strength dBuV/m */
6972 if (contour_threshold==0 || (ano>=(double)contour_threshold))
6974 ano=100.0+rint(ano);
6982 PutSignal(latitude,longitude,((unsigned char)round(ano)));
6986 s=fgets(string,78,fd);
6987 sscanf(string,"%lf, %lf, %lf, %lf, %lf",&latitude, &longitude, &azimuth, &elevation, &ano);
6992 fprintf(stdout," Done!\n");
7002 void WriteKML(struct site source, struct site destination)
7005 char block, report_name[80];
7006 double distance, rx_alt, tx_alt, cos_xmtr_angle,
7007 azimuth, cos_test_angle, test_alt;
7010 ReadPath(source,destination);
7012 sprintf(report_name,"%s-to-%s.kml",source.name,destination.name);
7014 for (x=0; report_name[x]!=0; x++)
7015 if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47)
7018 fd=fopen(report_name,"w");
7020 fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
7021 fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.0\">\n");
7022 fprintf(fd,"<!-- Generated by %s Version %s -->\n",splat_name, splat_version);
7023 fprintf(fd,"<Folder>\n");
7024 fprintf(fd,"<name>SPLAT! Path</name>\n");
7025 fprintf(fd,"<open>1</open>\n");
7026 fprintf(fd,"<description>Path Between %s and %s</description>\n",source.name,destination.name);
7028 fprintf(fd,"<Placemark>\n");
7029 fprintf(fd," <name>%s</name>\n",source.name);
7030 fprintf(fd," <description>\n");
7031 fprintf(fd," Transmit Site\n");
7033 if (source.lat>=0.0)
7034 fprintf(fd," <BR>%s North</BR>\n",dec2dms(source.lat));
7036 fprintf(fd," <BR>%s South</BR>\n",dec2dms(source.lat));
7038 fprintf(fd," <BR>%s West</BR>\n",dec2dms(source.lon));
7040 azimuth=Azimuth(source,destination);
7041 distance=Distance(source,destination);
7044 fprintf(fd," <BR>%.2f km",distance*KM_PER_MILE);
7046 fprintf(fd," <BR>%.2f miles",distance);
7048 fprintf(fd," to %s</BR>\n <BR>toward an azimuth of %.2f%c</BR>\n",destination.name,azimuth,176);
7050 fprintf(fd," </description>\n");
7051 fprintf(fd," <visibility>1</visibility>\n");
7052 fprintf(fd," <Style>\n");
7053 fprintf(fd," <IconStyle>\n");
7054 fprintf(fd," <Icon>\n");
7055 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
7056 fprintf(fd," <x>224</x>\n");
7057 fprintf(fd," <y>224</y>\n");
7058 fprintf(fd," <w>32</w>\n");
7059 fprintf(fd," <h>32</h>\n");
7060 fprintf(fd," </Icon>\n");
7061 fprintf(fd," </IconStyle>\n");
7062 fprintf(fd," </Style>\n");
7063 fprintf(fd," <Point>\n");
7064 fprintf(fd," <extrude>1</extrude>\n");
7065 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
7066 fprintf(fd," <coordinates>%f,%f,30</coordinates>\n",(source.lon<180.0?-source.lon:360.0-source.lon),source.lat);
7067 fprintf(fd," </Point>\n");
7068 fprintf(fd,"</Placemark>\n");
7070 fprintf(fd,"<Placemark>\n");
7071 fprintf(fd," <name>%s</name>\n",destination.name);
7072 fprintf(fd," <description>\n");
7073 fprintf(fd," Receive Site\n");
7075 if (destination.lat>=0.0)
7076 fprintf(fd," <BR>%s North</BR>\n",dec2dms(destination.lat));
7078 fprintf(fd," <BR>%s South</BR>\n",dec2dms(destination.lat));
7080 fprintf(fd," <BR>%s West</BR>\n",dec2dms(destination.lon));
7083 fprintf(fd," <BR>%.2f km",distance*KM_PER_MILE);
7085 fprintf(fd," <BR>%.2f miles",distance);
7087 fprintf(fd," to %s</BR>\n <BR>toward an azimuth of %.2f%c</BR>\n",source.name,Azimuth(destination,source),176);
7089 fprintf(fd," </description>\n");
7090 fprintf(fd," <visibility>1</visibility>\n");
7091 fprintf(fd," <Style>\n");
7092 fprintf(fd," <IconStyle>\n");
7093 fprintf(fd," <Icon>\n");
7094 fprintf(fd," <href>root://icons/palette-5.png</href>\n");
7095 fprintf(fd," <x>224</x>\n");
7096 fprintf(fd," <y>224</y>\n");
7097 fprintf(fd," <w>32</w>\n");
7098 fprintf(fd," <h>32</h>\n");
7099 fprintf(fd," </Icon>\n");
7100 fprintf(fd," </IconStyle>\n");
7101 fprintf(fd," </Style>\n");
7102 fprintf(fd," <Point>\n");
7103 fprintf(fd," <extrude>1</extrude>\n");
7104 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
7105 fprintf(fd," <coordinates>%f,%f,30</coordinates>\n",(destination.lon<180.0?-destination.lon:360.0-destination.lon),destination.lat);
7106 fprintf(fd," </Point>\n");
7107 fprintf(fd,"</Placemark>\n");
7109 fprintf(fd,"<Placemark>\n");
7110 fprintf(fd,"<name>Point-to-Point Path</name>\n");
7111 fprintf(fd," <visibility>1</visibility>\n");
7112 fprintf(fd," <open>0</open>\n");
7113 fprintf(fd," <Style>\n");
7114 fprintf(fd," <LineStyle>\n");
7115 fprintf(fd," <color>7fffffff</color>\n");
7116 fprintf(fd," </LineStyle>\n");
7117 fprintf(fd," <PolyStyle>\n");
7118 fprintf(fd," <color>7fffffff</color>\n");
7119 fprintf(fd," </PolyStyle>\n");
7120 fprintf(fd," </Style>\n");
7121 fprintf(fd," <LineString>\n");
7122 fprintf(fd," <extrude>1</extrude>\n");
7123 fprintf(fd," <tessellate>1</tessellate>\n");
7124 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
7125 fprintf(fd," <coordinates>\n");
7127 for (x=0; x<path.length; x++)
7128 fprintf(fd," %f,%f,5\n",(path.lon[x]<180.0?-path.lon[x]:360.0-path.lon[x]),path.lat[x]);
7130 fprintf(fd," </coordinates>\n");
7131 fprintf(fd," </LineString>\n");
7132 fprintf(fd,"</Placemark>\n");
7134 fprintf(fd,"<Placemark>\n");
7135 fprintf(fd,"<name>Line-of-Sight Path</name>\n");
7136 fprintf(fd," <visibility>1</visibility>\n");
7137 fprintf(fd," <open>0</open>\n");
7138 fprintf(fd," <Style>\n");
7139 fprintf(fd," <LineStyle>\n");
7140 fprintf(fd," <color>ff00ff00</color>\n");
7141 fprintf(fd," </LineStyle>\n");
7142 fprintf(fd," <PolyStyle>\n");
7143 fprintf(fd," <color>7f00ff00</color>\n");
7144 fprintf(fd," </PolyStyle>\n");
7145 fprintf(fd," </Style>\n");
7146 fprintf(fd," <LineString>\n");
7147 fprintf(fd," <extrude>1</extrude>\n");
7148 fprintf(fd," <tessellate>1</tessellate>\n");
7149 fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
7150 fprintf(fd," <coordinates>\n");
7152 /* Walk across the "path", indentifying obstructions along the way */
7154 for (y=0; y<path.length; y++)
7156 distance=5280.0*path.distance[y];
7157 tx_alt=earthradius+source.alt+path.elevation[0];
7158 rx_alt=earthradius+destination.alt+path.elevation[y];
7160 /* Calculate the cosine of the elevation of the
7161 transmitter as seen at the temp rx point. */
7163 cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance);
7165 for (x=y, block=0; x>=0 && block==0; x--)
7167 distance=5280.0*(path.distance[y]-path.distance[x]);
7168 test_alt=earthradius+path.elevation[x];
7170 cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance);
7172 /* Compare these two angles to determine if
7173 an obstruction exists. Since we're comparing
7174 the cosines of these angles rather than
7175 the angles themselves, the following "if"
7176 statement is reversed from what it would
7177 be if the actual angles were compared. */
7179 if (cos_xmtr_angle>=cos_test_angle)
7184 fprintf(fd," %f,%f,-30\n",(path.lon[y]<180.0?-path.lon[y]:360.0-path.lon[y]),path.lat[y]);
7186 fprintf(fd," %f,%f,5\n",(path.lon[y]<180.0?-path.lon[y]:360.0-path.lon[y]),path.lat[y]);
7189 fprintf(fd," </coordinates>\n");
7190 fprintf(fd," </LineString>\n");
7191 fprintf(fd,"</Placemark>\n");
7193 fprintf(fd," <LookAt>\n");
7194 fprintf(fd," <longitude>%f</longitude>\n",(source.lon<180.0?-source.lon:360.0-source.lon));
7195 fprintf(fd," <latitude>%f</latitude>\n",source.lat);
7196 fprintf(fd," <range>300.0</range>\n");
7197 fprintf(fd," <tilt>45.0</tilt>\n");
7198 fprintf(fd," <heading>%f</heading>\n",azimuth);
7199 fprintf(fd," </LookAt>\n");
7201 fprintf(fd,"</Folder>\n");
7202 fprintf(fd,"</kml>\n");
7206 fprintf(stdout, "\nKML file written to: \"%s\"",report_name);
7211 int main(int argc, char *argv[])
7213 int x, y, z=0, min_lat, min_lon, max_lat, max_lon,
7214 rxlat, rxlon, txlat, txlon, west_min, west_max,
7215 north_min, north_max;
7217 unsigned char coverage=0, LRmap=0, terrain_plot=0,
7218 elevation_plot=0, height_plot=0, map=0,
7219 longley_plot=0, cities=0, bfs=0, txsites=0,
7220 norm=0, topomap=0, geo=0, kml=0, pt2pt_mode=0,
7221 area_mode=0, max_txsites, ngs=0, nolospath=0,
7222 nositereports=0, fresnel_plot=1;
7224 char mapfile[255], header[80], city_file[5][255],
7225 elevation_file[255], height_file[255],
7226 longley_file[255], terrain_file[255],
7227 string[255], rxfile[255], *env=NULL,
7228 txfile[255], boundary_file[5][255],
7229 udt_file[255], rxsite=0, ani_filename[255],
7230 ano_filename[255], ext[20], *s=NULL;
7232 double altitude=0.0, altitudeLR=0.0, tx_range=0.0,
7233 rx_range=0.0, deg_range=0.0, deg_limit=0.0,
7234 deg_range_lon, er_mult;
7236 struct site tx_site[32], rx_site;
7240 strncpy(splat_version,"1.3.0\0",6);
7243 strncpy(splat_name,"SPLAT! HD\0",10);
7245 strncpy(splat_name,"SPLAT!\0",7);
7247 strncpy(dashes,"---------------------------------------------------------------------------\0",76);
7251 fprintf(stdout,"\n\t\t --==[ %s v%s Available Options... ]==--\n\n",splat_name, splat_version);
7253 fprintf(stdout," -t txsite(s).qth (max of 4 with -c, max of 30 with -L)\n");
7254 fprintf(stdout," -r rxsite.qth\n");
7255 fprintf(stdout," -c plot coverage of TX(s) with an RX antenna at X feet/meters AGL\n");
7256 fprintf(stdout," -L plot path loss map of TX based on an RX at X feet/meters AGL\n");
7257 fprintf(stdout," -s filename(s) of city/site file(s) to import (5 max)\n");
7258 fprintf(stdout," -b filename(s) of cartographic boundary file(s) to import (5 max)\n");
7259 fprintf(stdout," -p filename of terrain profile graph to plot\n");
7260 fprintf(stdout," -e filename of terrain elevation graph to plot\n");
7261 fprintf(stdout," -h filename of terrain height graph to plot\n");
7262 fprintf(stdout," -H filename of normalized terrain height graph to plot\n");
7263 fprintf(stdout," -l filename of path loss graph to plot\n");
7264 fprintf(stdout," -o filename of topographic map to generate (.ppm)\n");
7265 fprintf(stdout," -u filename of user-defined terrain file to import\n");
7266 fprintf(stdout," -d sdf file directory path (overrides path in ~/.splat_path file)\n");
7267 fprintf(stdout," -m earth radius multiplier\n");
7268 fprintf(stdout," -n do not plot LOS paths in .ppm maps\n");
7269 fprintf(stdout," -N do not produce unnecessary site or obstruction reports\n");
7270 fprintf(stdout," -f frequency for Fresnel zone calculation (MHz)\n");
7271 fprintf(stdout," -R modify default range for -c or -L (miles/kilometers)\n");
7272 fprintf(stdout," -db threshold beyond which contours will not be displayed\n");
7273 fprintf(stdout," -nf do not plot Fresnel zones in height plots\n");
7274 fprintf(stdout," -fz Fresnel zone clearance percentage (default = 60)\n");
7275 fprintf(stdout," -gc ground clutter height (feet/meters)\n");
7276 fprintf(stdout," -ngs display greyscale topography as white in .ppm files\n");
7277 fprintf(stdout," -erp override ERP in .lrp file (Watts)\n");
7278 fprintf(stdout," -ano name of alphanumeric output file\n");
7279 fprintf(stdout," -ani name of alphanumeric input file\n");
7280 fprintf(stdout," -udt name of user defined terrain input file\n");
7281 fprintf(stdout," -kml generate Google Earth (.kml) compatible output\n");
7282 fprintf(stdout," -geo generate an Xastir .geo georeference file (with .ppm output)\n");
7283 fprintf(stdout," -dbm plot signal power level contours rather than field strength\n");
7284 fprintf(stdout," -gpsav preserve gnuplot temporary working files after SPLAT! execution\n");
7285 fprintf(stdout," -metric employ metric rather than imperial units for all user I/O\n\n");
7286 fprintf(stdout,"If that flew by too fast, consider piping the output through 'less':\n");
7289 fprintf(stdout,"\n\tsplat | less\n\n");
7291 fprintf(stdout,"\n\tsplat-hd | less\n\n");
7293 fprintf(stdout,"Type 'man splat', or see the documentation for more details.\n\n");
7295 y=(int)sqrt((int)MAXPAGES);
7297 fprintf(stdout,"This compilation of %s supports analysis over a region of\n%d square ",splat_name,y);
7301 fprintf(stdout,"degree");
7303 fprintf(stdout,"degrees");
7305 fprintf(stdout," of terrain.\n\n");
7325 elevation_file[0]=0;
7331 fzone_clearance=0.6;
7332 contour_threshold=0;
7338 earthradius=EARTHRADIUS;
7340 ippd=IPPD; /* pixels per degree (integer) */
7341 ppd=(double)ippd; /* pixels per degree (double) */
7342 dpp=1.0/ppd; /* degrees per pixel */
7343 mpi=ippd-1; /* maximum pixel index per degree */
7345 sprintf(header,"\n\t\t--==[ Welcome To %s v%s ]==--\n\n", splat_name, splat_version);
7349 tx_site[x].lat=91.0;
7350 tx_site[x].lon=361.0;
7353 for (x=0; x<MAXPAGES; x++)
7355 dem[x].min_el=32768;
7356 dem[x].max_el=-32768;
7357 dem[x].min_north=90;
7358 dem[x].max_north=-90;
7359 dem[x].min_west=360;
7363 /* Scan for command line arguments */
7365 for (x=1; x<=y; x++)
7367 if (strcmp(argv[x],"-R")==0)
7371 if (z<=y && argv[z][0] && argv[z][0]!='-')
7373 sscanf(argv[z],"%lf",&max_range);
7378 if (max_range>1000.0)
7383 if (strcmp(argv[x],"-m")==0)
7387 if (z<=y && argv[z][0] && argv[z][0]!='-')
7389 sscanf(argv[z],"%lf",&er_mult);
7397 earthradius*=er_mult;
7401 if (strcmp(argv[x],"-gc")==0)
7405 if (z<=y && argv[z][0] && argv[z][0]!='-')
7407 sscanf(argv[z],"%lf",&clutter);
7414 if (strcmp(argv[x],"-fz")==0)
7418 if (z<=y && argv[z][0] && argv[z][0]!='-')
7420 sscanf(argv[z],"%lf",&fzone_clearance);
7422 if (fzone_clearance<0.0 || fzone_clearance>100.0)
7423 fzone_clearance=60.0;
7425 fzone_clearance/=100.0;
7429 if (strcmp(argv[x],"-o")==0)
7433 if (z<=y && argv[z][0] && argv[z][0]!='-')
7434 strncpy(mapfile,argv[z],253);
7438 if (strcmp(argv[x],"-udt")==0)
7442 if (z<=y && argv[z][0] && argv[z][0]!='-')
7443 strncpy(udt_file,argv[z],253);
7446 if (strcmp(argv[x],"-c")==0)
7450 if (z<=y && argv[z][0] && argv[z][0]!='-')
7452 sscanf(argv[z],"%lf",&altitude);
7460 if (strcmp(argv[x],"-db")==0 || strcmp(argv[x],"-dB")==0)
7464 if (z<=y && argv[z][0]) /* A minus argument is legal here */
7465 sscanf(argv[z],"%d",&contour_threshold);
7468 if (strcmp(argv[x],"-p")==0)
7472 if (z<=y && argv[z][0] && argv[z][0]!='-')
7474 strncpy(terrain_file,argv[z],253);
7480 if (strcmp(argv[x],"-e")==0)
7484 if (z<=y && argv[z][0] && argv[z][0]!='-')
7486 strncpy(elevation_file,argv[z],253);
7492 if (strcmp(argv[x],"-h")==0 || strcmp(argv[x],"-H")==0)
7496 if (z<=y && argv[z][0] && argv[z][0]!='-')
7498 strncpy(height_file,argv[z],253);
7503 if (strcmp(argv[x],"-H")==0)
7509 if (strcmp(argv[x],"-metric")==0)
7512 if (strcmp(argv[x],"-gpsav")==0)
7515 if (strcmp(argv[x],"-geo")==0)
7518 if (strcmp(argv[x],"-kml")==0)
7521 if (strcmp(argv[x],"-nf")==0)
7524 if (strcmp(argv[x],"-ngs")==0)
7527 if (strcmp(argv[x],"-n")==0)
7530 if (strcmp(argv[x],"-dbm")==0)
7533 if (strcmp(argv[x],"-N")==0)
7539 if (strcmp(argv[x],"-d")==0)
7543 if (z<=y && argv[z][0] && argv[z][0]!='-')
7544 strncpy(sdf_path,argv[z],253);
7547 if (strcmp(argv[x],"-t")==0)
7549 /* Read Transmitter Location */
7553 while (z<=y && argv[z][0] && argv[z][0]!='-' && txsites<30)
7555 strncpy(txfile,argv[z],253);
7556 tx_site[txsites]=LoadQTH(txfile);
7564 if (strcmp(argv[x],"-L")==0)
7568 if (z<=y && argv[z][0] && argv[z][0]!='-')
7570 sscanf(argv[z],"%lf",&altitudeLR);
7576 fprintf(stdout,"c and L are exclusive options, ignoring L.\n");
7580 if (strcmp(argv[x],"-l")==0)
7584 if (z<=y && argv[z][0] && argv[z][0]!='-')
7586 strncpy(longley_file,argv[z],253);
7592 if (strcmp(argv[x],"-r")==0)
7594 /* Read Receiver Location */
7598 if (z<=y && argv[z][0] && argv[z][0]!='-')
7600 strncpy(rxfile,argv[z],253);
7601 rx_site=LoadQTH(rxfile);
7607 if (strcmp(argv[x],"-s")==0)
7609 /* Read city file(s) */
7613 while (z<=y && argv[z][0] && argv[z][0]!='-' && cities<5)
7615 strncpy(city_file[cities],argv[z],253);
7623 if (strcmp(argv[x],"-b")==0)
7625 /* Read Boundary File(s) */
7629 while (z<=y && argv[z][0] && argv[z][0]!='-' && bfs<5)
7631 strncpy(boundary_file[bfs],argv[z],253);
7639 if (strcmp(argv[x],"-f")==0)
7643 if (z<=y && argv[z][0] && argv[z][0]!='-')
7645 sscanf(argv[z],"%lf",&forced_freq);
7647 if (forced_freq<20.0)
7650 if (forced_freq>20.0e3)
7655 if (strcmp(argv[x],"-erp")==0)
7659 if (z<=y && argv[z][0] && argv[z][0]!='-')
7661 sscanf(argv[z],"%lf",&forced_erp);
7668 if (strcmp(argv[x],"-ano")==0)
7672 if (z<=y && argv[z][0] && argv[z][0]!='-')
7673 strncpy(ano_filename,argv[z],253);
7676 if (strcmp(argv[x],"-ani")==0)
7680 if (z<=y && argv[z][0] && argv[z][0]!='-')
7681 strncpy(ani_filename,argv[z],253);
7685 /* Perform some error checking on the arguments
7686 and switches parsed from the command-line.
7687 If an error is encountered, print a message
7688 and exit gracefully. */
7692 fprintf(stderr,"\n%c*** ERROR: No transmitter site(s) specified!\n\n",7);
7696 for (x=0, y=0; x<txsites; x++)
7698 if (tx_site[x].lat==91.0 && tx_site[x].lon==361.0)
7700 fprintf(stderr,"\n*** ERROR: Transmitter site #%d not found!",x+1);
7707 fprintf(stderr,"%c\n\n",7);
7711 if ((coverage+LRmap+ani_filename[0])==0 && rx_site.lat==91.0 && rx_site.lon==361.0)
7713 if (max_range!=0.0 && txsites!=0)
7715 /* Plot topographic map of radius "max_range" */
7723 fprintf(stderr,"\n%c*** ERROR: No receiver site found or specified!\n\n",7);
7728 /* No major errors were detected. Whew! :-) */
7730 /* Adjust input parameters if -metric option is used */
7734 altitudeLR/=METERS_PER_FOOT; /* meters --> feet */
7735 max_range/=KM_PER_MILE; /* kilometers --> miles */
7736 altitude/=METERS_PER_FOOT; /* meters --> feet */
7737 clutter/=METERS_PER_FOOT; /* meters --> feet */
7740 /* If no SDF path was specified on the command line (-d), check
7741 for a path specified in the $HOME/.splat_path file. If the
7742 file is not found, then sdf_path[] remains NULL, and the
7743 current working directory is assumed to contain the SDF
7749 snprintf(string,253,"%s/.splat_path",env);
7750 fd=fopen(string,"r");
7754 s=fgets(string,253,fd);
7756 /* Remove <CR> and/or <LF> from string */
7758 for (x=0; string[x]!=13 && string[x]!=10 && string[x]!=0 && x<253; x++);
7761 strncpy(sdf_path,string,253);
7767 /* Ensure a trailing '/' is present in sdf_path */
7773 if (sdf_path[x-1]!='/' && x!=0)
7780 fprintf(stdout,"%s",header);
7783 if (ani_filename[0])
7785 ReadLRParm(tx_site[0],0); /* Get ERP status */
7786 y=LoadANO(ani_filename);
7788 for (x=0; x<txsites && x<max_txsites; x++)
7789 PlaceMarker(tx_site[x]);
7792 PlaceMarker(rx_site);
7796 for (x=0; x<bfs; x++)
7797 LoadBoundaries(boundary_file[x]);
7799 fprintf(stdout,"\n");
7805 for (x=0; x<cities; x++)
7806 LoadCities(city_file[x]);
7808 fprintf(stdout,"\n");
7813 WritePPMLR(mapfile,geo,kml,ngs,tx_site,txsites);
7817 WritePPMDBM(mapfile,geo,kml,ngs,tx_site,txsites);
7819 WritePPMSS(mapfile,geo,kml,ngs,tx_site,txsites);
7831 min_lon=(int)floor(tx_site[0].lon);
7832 max_lon=(int)floor(tx_site[0].lon);
7834 for (y=0, z=0; z<txsites && z<max_txsites; z++)
7836 txlat=(int)floor(tx_site[z].lat);
7837 txlon=(int)floor(tx_site[z].lon);
7845 if (LonDiff(txlon,min_lon)<0.0)
7848 if (LonDiff(txlon,max_lon)>=0.0)
7854 rxlat=(int)floor(rx_site.lat);
7855 rxlon=(int)floor(rx_site.lon);
7863 if (LonDiff(rxlon,min_lon)<0.0)
7866 if (LonDiff(rxlon,max_lon)>=0.0)
7870 /* Load the required SDF files */
7872 LoadTopoData(max_lon, min_lon, max_lat, min_lat);
7874 if (area_mode || topomap)
7876 for (z=0; z<txsites && z<max_txsites; z++)
7878 /* "Ball park" estimates used to load any additional
7879 SDF files required to conduct this analysis. */
7881 tx_range=sqrt(1.5*(tx_site[z].alt+GetElevation(tx_site[z])));
7884 rx_range=sqrt(1.5*altitudeLR);
7886 rx_range=sqrt(1.5*altitude);
7888 /* deg_range determines the maximum
7889 amount of topo data we read */
7891 deg_range=(tx_range+rx_range)/57.0;
7893 /* max_range regulates the size of the
7894 analysis. A small, non-zero amount can
7895 be used to shrink the size of the analysis
7896 and limit the amount of topo data read by
7897 SPLAT! A large number will increase the
7898 width of the analysis and the size of
7902 max_range=tx_range+rx_range;
7904 deg_range=max_range/57.0;
7906 /* Prevent the demand for a really wide coverage
7907 from allocating more "pages" than are available
7912 case 1: deg_limit=0.125;
7915 case 2: deg_limit=0.25;
7918 case 4: deg_limit=0.5;
7921 case 9: deg_limit=1.0;
7924 case 16: deg_limit=1.5; /* WAS 2.0 */
7927 case 25: deg_limit=2.0; /* WAS 3.0 */
7930 case 36: deg_limit=2.5; /* New! */
7933 case 49: deg_limit=3.0; /* New! */
7936 case 64: deg_limit=3.5; /* New! */
7940 if (fabs(tx_site[z].lat)<70.0)
7941 deg_range_lon=deg_range/cos(DEG2RAD*tx_site[z].lat);
7943 deg_range_lon=deg_range/cos(DEG2RAD*70.0);
7945 /* Correct for squares in degrees not being square in miles */
7947 if (deg_range>deg_limit)
7948 deg_range=deg_limit;
7950 if (deg_range_lon>deg_limit)
7951 deg_range_lon=deg_limit;
7953 north_min=(int)floor(tx_site[z].lat-deg_range);
7954 north_max=(int)floor(tx_site[z].lat+deg_range);
7956 west_min=(int)floor(tx_site[z].lon-deg_range_lon);
7961 while (west_min>=360)
7964 west_max=(int)floor(tx_site[z].lon+deg_range_lon);
7969 while (west_max>=360)
7972 if (north_min<min_lat)
7975 if (north_max>max_lat)
7978 if (LonDiff(west_min,min_lon)<0.0)
7981 if (LonDiff(west_max,max_lon)>=0.0)
7985 /* Load any additional SDF files, if required */
7987 LoadTopoData(max_lon, min_lon, max_lat, min_lat);
7994 /***** Let the SPLATting begin! *****/
7998 PlaceMarker(rx_site);
8002 /* Extract extension (if present)
8003 from "terrain_file" */
8005 y=strlen(terrain_file);
8007 for (x=y-1; x>0 && terrain_file[x]!='.'; x--);
8009 if (x>0) /* Extension found */
8011 for (z=x+1; z<=y && (z-(x+1))<10; z++)
8012 ext[z-(x+1)]=tolower(terrain_file[z]);
8014 ext[z-(x+1)]=0; /* Ensure an ending 0 */
8015 terrain_file[x]=0; /* Chop off extension */
8019 strncpy(ext,"png\0",4);
8024 /* Extract extension (if present)
8025 from "elevation_file" */
8027 y=strlen(elevation_file);
8029 for (x=y-1; x>0 && elevation_file[x]!='.'; x--);
8031 if (x>0) /* Extension found */
8033 for (z=x+1; z<=y && (z-(x+1))<10; z++)
8034 ext[z-(x+1)]=tolower(elevation_file[z]);
8036 ext[z-(x+1)]=0; /* Ensure an ending 0 */
8037 elevation_file[x]=0; /* Chop off extension */
8041 strncpy(ext,"png\0",4);
8046 /* Extract extension (if present)
8047 from "height_file" */
8049 y=strlen(height_file);
8051 for (x=y-1; x>0 && height_file[x]!='.'; x--);
8053 if (x>0) /* Extension found */
8055 for (z=x+1; z<=y && (z-(x+1))<10; z++)
8056 ext[z-(x+1)]=tolower(height_file[z]);
8058 ext[z-(x+1)]=0; /* Ensure an ending 0 */
8059 height_file[x]=0; /* Chop off extension */
8063 strncpy(ext,"png\0",4);
8068 /* Extract extension (if present)
8069 from "longley_file" */
8071 y=strlen(longley_file);
8073 for (x=y-1; x>0 && longley_file[x]!='.'; x--);
8075 if (x>0) /* Extension found */
8077 for (z=x+1; z<=y && (z-(x+1))<10; z++)
8078 ext[z-(x+1)]=tolower(longley_file[z]);
8080 ext[z-(x+1)]=0; /* Ensure an ending 0 */
8081 longley_file[x]=0; /* Chop off extension */
8085 strncpy(ext,"png\0",4);
8088 for (x=0; x<txsites && x<4; x++)
8090 PlaceMarker(tx_site[x]);
8097 PlotPath(tx_site[x],rx_site,1);
8101 PlotPath(tx_site[x],rx_site,8);
8105 PlotPath(tx_site[x],rx_site,16);
8109 PlotPath(tx_site[x],rx_site,32);
8113 if (nositereports==0)
8114 SiteReport(tx_site[x]);
8117 WriteKML(tx_site[x],rx_site);
8120 snprintf(string,250,"%s-%c.%s%c",longley_file,'1'+x,ext,0);
8122 snprintf(string,250,"%s.%s%c",longley_file,ext,0);
8124 if (nositereports==0)
8126 if (longley_file[0]==0)
8128 ReadLRParm(tx_site[x],0);
8129 PathReport(tx_site[x],rx_site,string,0);
8134 ReadLRParm(tx_site[x],1);
8135 PathReport(tx_site[x],rx_site,string,longley_file[0]);
8142 snprintf(string,250,"%s-%c.%s%c",terrain_file,'1'+x,ext,0);
8144 snprintf(string,250,"%s.%s%c",terrain_file,ext,0);
8146 GraphTerrain(tx_site[x],rx_site,string);
8152 snprintf(string,250,"%s-%c.%s%c",elevation_file,'1'+x,ext,0);
8154 snprintf(string,250,"%s.%s%c",elevation_file,ext,0);
8156 GraphElevation(tx_site[x],rx_site,string);
8162 snprintf(string,250,"%s-%c.%s%c",height_file,'1'+x,ext,0);
8164 snprintf(string,250,"%s.%s%c",height_file,ext,0);
8166 GraphHeight(tx_site[x],rx_site,string,fresnel_plot,norm);
8171 if (area_mode && topomap==0)
8173 for (x=0; x<txsites && x<max_txsites; x++)
8176 PlotLOSMap(tx_site[x],altitude);
8178 else if (ReadLRParm(tx_site[x],1))
8179 PlotLRMap(tx_site[x],altitudeLR,ano_filename);
8181 SiteReport(tx_site[x]);
8191 for (x=0; x<txsites && x<max_txsites; x++)
8192 PlaceMarker(tx_site[x]);
8198 for (y=0; y<cities; y++)
8199 LoadCities(city_file[y]);
8201 fprintf(stdout,"\n");
8205 /* Load city and county boundary data files */
8209 for (y=0; y<bfs; y++)
8210 LoadBoundaries(boundary_file[y]);
8212 fprintf(stdout,"\n");
8218 if (coverage || pt2pt_mode || topomap)
8219 WritePPM(mapfile,geo,kml,ngs,tx_site,txsites);
8224 WritePPMLR(mapfile,geo,kml,ngs,tx_site,txsites);
8227 WritePPMDBM(mapfile,geo,kml,ngs,tx_site,txsites);
8229 WritePPMSS(mapfile,geo,kml,ngs,tx_site,txsites);
8235 /* That's all, folks! */