+ fclose(fd);
+ }
+
+ fd=fopen(mapfile,"wb");
+
+ fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
+ fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
+ fflush(stdout);
+
+ for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y))
+ {
+ for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x))
+ {
+ if (lon<0.0)
+ lon+=360.0;
+
+ for (indx=0, found=0; indx<MAXPAGES && found==0;)
+ {
+ x0=(int)rint(ppd*(lat-(double)dem[indx].min_north));
+ y0=mpi-(int)rint(ppd*(LonDiff((double)dem[indx].max_west,lon)));
+
+ if (x0>=0 && x0<=mpi && y0>=0 && y0<=mpi)
+ found=1;
+ else
+ indx++;
+ }
+
+ if (found)
+ {
+ mask=dem[indx].mask[x0][y0];
+ loss=(dem[indx].signal[x0][y0]);
+ cityorcounty=0;
+
+ match=255;
+
+ red=0;
+ green=0;
+ blue=0;
+
+ if (loss<=region.level[0])
+ match=0;
+ else
+ {
+ for (z=1; (z<region.levels && match==255); z++)
+ {
+ if (loss>=region.level[z-1] && loss<region.level[z])
+ match=z;
+ }
+ }
+
+ if (match<region.levels)
+ {
+ red=region.color[match][0];
+ green=region.color[match][1];
+ blue=region.color[match][2];
+ }
+
+ if (mask&2)
+ {
+ /* Text Labels: Red or otherwise */
+
+ if (red>=180 && green<=75 && blue<=75 && loss!=0)
+ fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
+ else
+ fprintf(fd,"%c%c%c",255,0,0);
+
+ cityorcounty=1;
+ }
+
+ else if (mask&4)
+ {
+ /* County Boundaries: Black */
+
+ fprintf(fd,"%c%c%c",0,0,0);
+
+ cityorcounty=1;
+ }
+
+ if (cityorcounty==0)
+ {
+ if (loss==0 || (contour_threshold!=0 && loss>abs(contour_threshold)))
+ {
+ if (ngs) /* No terrain */
+ fprintf(fd,"%c%c%c",255,255,255);
+ else
+ {
+ /* Display land or sea elevation */
+
+ if (dem[indx].data[x0][y0]==0)
+ fprintf(fd,"%c%c%c",0,0,170);
+ else
+ {
+ terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
+ fprintf(fd,"%c%c%c",terrain,terrain,terrain);
+ }
+ }
+ }
+
+ else
+ {
+ /* Plot path loss in color */
+
+ if (red!=0 || green!=0 || blue!=0)
+ fprintf(fd,"%c%c%c",red,green,blue);
+
+ else /* terrain / sea-level */
+ {
+ if (dem[indx].data[x0][y0]==0)
+ fprintf(fd,"%c%c%c",0,0,170);
+ else
+ {
+ /* Elevation: Greyscale */
+ terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
+ fprintf(fd,"%c%c%c",terrain,terrain,terrain);
+ }
+ }
+ }
+ }
+ }
+
+ else
+ {
+ /* We should never get here, but if */
+ /* we do, display the region as black */
+
+ fprintf(fd,"%c%c%c",0,0,0);
+ }
+ }
+ }
+
+ if (kml==0 && geo==0)
+ {
+ /* Display legend along bottom of image
+ * if not generating .kml or .geo output.
+ */
+
+ colorwidth=(int)rint((float)width/(float)region.levels);
+
+ for (y0=0; y0<30; y0++)
+ {
+ for (x0=0; x0<(int)width; x0++)
+ {
+ indx=x0/colorwidth;
+ x=x0%colorwidth;
+ level=region.level[indx];
+
+ hundreds=level/100;
+
+ if (hundreds>0)
+ level-=(hundreds*100);
+
+ tens=level/10;
+
+ if (tens>0)
+ level-=(tens*10);
+
+ units=level;
+
+ if (y0>=8 && y0<=23)
+ {
+ if (hundreds>0)
+ {
+ if (x>=11 && x<=18)
+ if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-11)))
+ indx=255;
+ }
+
+ if (tens>0 || hundreds>0)
+ {
+ if (x>=19 && x<=26)
+ if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-19)))
+ indx=255;
+ }
+
+ if (x>=27 && x<=34)
+ if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-27)))
+ indx=255;
+
+ if (x>=42 && x<=49)
+ if (fontdata[16*('d')+(y0-8)]&(128>>(x-42)))
+ indx=255;
+
+ if (x>=50 && x<=57)
+ if (fontdata[16*('B')+(y0-8)]&(128>>(x-50)))
+ indx=255;
+ }
+
+ if (indx>region.levels)
+ fprintf(fd,"%c%c%c",0,0,0);
+ else
+ {
+ red=region.color[indx][0];
+ green=region.color[indx][1];
+ blue=region.color[indx][2];
+
+ fprintf(fd,"%c%c%c",red,green,blue);
+ }
+ }
+ }
+ }
+
+ fclose(fd);
+ fprintf(stdout,"Done!\n");
+ fflush(stdout);
+}
+
+void WritePPMSS(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
+{
+ /* This function generates a topographic map in Portable Pix Map
+ (PPM) format based on the signal strength values held in the
+ signal[][] array. The image created is rotated counter-clockwise
+ 90 degrees from its representation in dem[][] so that north
+ points up and east points right in the image generated. */
+
+ char mapfile[255], geofile[255], kmlfile[255];
+ unsigned width, height, terrain, red, green, blue;
+ unsigned char found, mask, cityorcounty;
+ int indx, x, y, z=1, x0, y0, signal, level, hundreds,
+ tens, units, match, colorwidth;
+ double conversion, one_over_gamma, lat, lon,
+ north, south, east, west, minwest;
+ FILE *fd;
+
+ one_over_gamma=1.0/GAMMA;
+ conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
+
+ width=(unsigned)(ippd*ReduceAngle(max_west-min_west));
+ height=(unsigned)(ippd*ReduceAngle(max_north-min_north));
+
+ LoadSignalColors(xmtr[0]);
+
+ if (filename[0]==0)
+ {
+ strncpy(filename, xmtr[0].filename,254);
+ filename[strlen(filename)-4]=0; /* Remove .qth */
+ }
+
+ y=strlen(filename);
+
+ if (y>4)
+ {
+ if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.')
+ y-=4;
+ }
+
+ for (x=0; x<y; x++)
+ {
+ mapfile[x]=filename[x];
+ geofile[x]=filename[x];
+ kmlfile[x]=filename[x];
+ }
+
+ mapfile[x]='.';
+ geofile[x]='.';
+ kmlfile[x]='.';
+ mapfile[x+1]='p';
+ geofile[x+1]='g';
+ kmlfile[x+1]='k';
+ mapfile[x+2]='p';
+ geofile[x+2]='e';
+ kmlfile[x+2]='m';
+ mapfile[x+3]='m';
+ geofile[x+3]='o';
+ kmlfile[x+3]='l';
+ mapfile[x+4]=0;
+ geofile[x+4]=0;
+ kmlfile[x+4]=0;
+
+ minwest=((double)min_west)+dpp;
+
+ if (minwest>360.0)
+ minwest-=360.0;
+
+ north=(double)max_north-dpp;
+
+ if (kml || geo)
+ south=(double)min_north; /* No bottom legend */
+ else
+ south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */
+
+ east=(minwest<180.0?-minwest:360.0-min_west);
+ west=(double)(max_west<180?-max_west:360-max_west);
+
+ if (geo && kml==0)
+ {
+ fd=fopen(geofile,"wb");
+
+ fprintf(fd,"FILENAME\t%s\n",mapfile);
+ fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
+ fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north);
+
+ fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south);
+ fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height);
+
+ fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version);
+
+ fclose(fd);
+ }
+
+ if (kml && geo==0)
+ {
+ fd=fopen(kmlfile,"wb");
+
+ fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
+ fprintf(fd,"<!-- Generated by %s Version %s -->\n",splat_name,splat_version);
+ fprintf(fd," <Folder>\n");
+ fprintf(fd," <name>%s</name>\n",splat_name);
+ fprintf(fd," <description>%s Transmitter Coverage Overlay</description>\n",xmtr[0].name);
+ fprintf(fd," <GroundOverlay>\n");
+ fprintf(fd," <name>SPLAT! Signal Strength Overlay</name>\n");
+ fprintf(fd," <description>SPLAT! Coverage</description>\n");
+ fprintf(fd," <Icon>\n");
+ fprintf(fd," <href>%s</href>\n",mapfile);
+ fprintf(fd," </Icon>\n");
+ fprintf(fd," <opacity>128</opacity>\n");
+ fprintf(fd," <LatLonBox>\n");
+ fprintf(fd," <north>%.5f</north>\n",north);
+ fprintf(fd," <south>%.5f</south>\n",south);
+ fprintf(fd," <east>%.5f</east>\n",east);
+ fprintf(fd," <west>%.5f</west>\n",west);
+ fprintf(fd," <rotation>0.0</rotation>\n");
+ fprintf(fd," </LatLonBox>\n");
+ fprintf(fd," </GroundOverlay>\n");
+
+ for (x=0; x<txsites; x++)
+ {
+ fprintf(fd," <Placemark>\n");
+ fprintf(fd," <name>%s</name>\n",xmtr[x].name);
+ fprintf(fd," <visibility>1</visibility>\n");
+ fprintf(fd," <Style>\n");
+ fprintf(fd," <IconStyle>\n");
+ fprintf(fd," <Icon>\n");
+ fprintf(fd," <href>root://icons/palette-5.png</href>\n");
+ fprintf(fd," <x>224</x>\n");
+ fprintf(fd," <y>224</y>\n");
+ fprintf(fd," <w>32</w>\n");
+ fprintf(fd," <h>32</h>\n");
+ fprintf(fd," </Icon>\n");
+ fprintf(fd," </IconStyle>\n");
+ fprintf(fd," </Style>\n");
+ fprintf(fd," <Point>\n");
+ fprintf(fd," <extrude>1</extrude>\n");
+ fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
+ 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);
+ fprintf(fd," </Point>\n");
+ fprintf(fd," </Placemark>\n");
+ }
+
+ fprintf(fd," </Folder>\n");
+ fprintf(fd,"</kml>\n");
+
+ fclose(fd);
+ }
+
+ fd=fopen(mapfile,"wb");
+
+ fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
+ fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
+ fflush(stdout);
+
+ for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y))
+ {
+ for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x))
+ {
+ if (lon<0.0)
+ lon+=360.0;
+
+ for (indx=0, found=0; indx<MAXPAGES && found==0;)
+ {
+ x0=(int)rint(ppd*(lat-(double)dem[indx].min_north));
+ y0=mpi-(int)rint(ppd*(LonDiff((double)dem[indx].max_west,lon)));
+
+ if (x0>=0 && x0<=mpi && y0>=0 && y0<=mpi)
+ found=1;
+ else
+ indx++;
+ }
+
+ if (found)
+ {
+ mask=dem[indx].mask[x0][y0];
+ signal=(dem[indx].signal[x0][y0])-100;
+ cityorcounty=0;
+
+ match=255;
+
+ red=0;
+ green=0;
+ blue=0;
+
+ if (signal>=region.level[0])
+ match=0;
+ else
+ {
+ for (z=1; (z<region.levels && match==255); z++)
+ {
+ if (signal<region.level[z-1] && signal>=region.level[z])
+ match=z;
+ }
+ }
+
+ if (match<region.levels)
+ {
+ red=region.color[match][0];
+ green=region.color[match][1];
+ blue=region.color[match][2];
+ }
+
+ if (mask&2)
+ {
+ /* Text Labels: Red or otherwise */
+
+ if (red>=180 && green<=75 && blue<=75)
+ fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
+ else
+ fprintf(fd,"%c%c%c",255,0,0);
+
+ cityorcounty=1;
+ }
+
+ else if (mask&4)
+ {
+ /* County Boundaries: Black */
+
+ fprintf(fd,"%c%c%c",0,0,0);
+
+ cityorcounty=1;
+ }
+
+ if (cityorcounty==0)
+ {
+ if (contour_threshold!=0 && signal<contour_threshold)
+ {
+ if (ngs)
+ fprintf(fd,"%c%c%c",255,255,255);
+ else
+ {
+ /* Display land or sea elevation */
+
+ if (dem[indx].data[x0][y0]==0)
+ fprintf(fd,"%c%c%c",0,0,170);
+ else
+ {
+ terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
+ fprintf(fd,"%c%c%c",terrain,terrain,terrain);
+ }
+ }
+ }
+
+ else
+ {
+ /* Plot field strength regions in color */
+
+ if (red!=0 || green!=0 || blue!=0)
+ fprintf(fd,"%c%c%c",red,green,blue);
+
+ else /* terrain / sea-level */
+ {
+ if (ngs)
+ fprintf(fd,"%c%c%c",255,255,255);
+ else
+ {
+ if (dem[indx].data[x0][y0]==0)
+ fprintf(fd,"%c%c%c",0,0,170);
+ else
+ {
+ /* Elevation: Greyscale */
+ terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
+ fprintf(fd,"%c%c%c",terrain,terrain,terrain);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ else
+ {
+ /* We should never get here, but if */
+ /* we do, display the region as black */
+
+ fprintf(fd,"%c%c%c",0,0,0);
+ }
+ }
+ }
+
+ if (kml==0 && geo==0)
+ {
+ /* Display legend along bottom of image
+ * if not generating .kml or .geo output.
+ */
+
+ colorwidth=(int)rint((float)width/(float)region.levels);
+
+ for (y0=0; y0<30; y0++)
+ {
+ for (x0=0; x0<(int)width; x0++)
+ {
+ indx=x0/colorwidth;
+ x=x0%colorwidth;
+ level=region.level[indx];
+
+ hundreds=level/100;
+
+ if (hundreds>0)
+ level-=(hundreds*100);
+
+ tens=level/10;
+
+ if (tens>0)
+ level-=(tens*10);
+
+ units=level;
+
+ if (y0>=8 && y0<=23)
+ {
+ if (hundreds>0)
+ {
+ if (x>=5 && x<=12)
+ if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-5)))
+ indx=255;
+ }
+
+ if (tens>0 || hundreds>0)
+ {
+ if (x>=13 && x<=20)
+ if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-13)))
+ indx=255;
+ }
+
+ if (x>=21 && x<=28)
+ if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-21)))
+ indx=255;
+
+ if (x>=36 && x<=43)
+ if (fontdata[16*('d')+(y0-8)]&(128>>(x-36)))
+ indx=255;
+
+ if (x>=44 && x<=51)
+ if (fontdata[16*('B')+(y0-8)]&(128>>(x-44)))
+ indx=255;
+
+ if (x>=52 && x<=59)
+ if (fontdata[16*(230)+(y0-8)]&(128>>(x-52)))
+ indx=255;
+
+ if (x>=60 && x<=67)
+ if (fontdata[16*('V')+(y0-8)]&(128>>(x-60)))
+ indx=255;
+
+ if (x>=68 && x<=75)
+ if (fontdata[16*('/')+(y0-8)]&(128>>(x-68)))
+ indx=255;
+
+ if (x>=76 && x<=83)
+ if (fontdata[16*('m')+(y0-8)]&(128>>(x-76)))
+ indx=255;
+ }
+
+ if (indx>region.levels)
+ fprintf(fd,"%c%c%c",0,0,0);
+ else
+ {
+ red=region.color[indx][0];
+ green=region.color[indx][1];
+ blue=region.color[indx][2];
+
+ fprintf(fd,"%c%c%c",red,green,blue);
+ }
+ }
+ }
+ }
+
+ fclose(fd);
+ fprintf(stdout,"Done!\n");
+ fflush(stdout);
+}
+
+void WritePPMDBM(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites)
+{
+ /* This function generates a topographic map in Portable Pix Map
+ (PPM) format based on the signal power level values held in the
+ signal[][] array. The image created is rotated counter-clockwise
+ 90 degrees from its representation in dem[][] so that north
+ points up and east points right in the image generated. */
+
+ char mapfile[255], geofile[255], kmlfile[255];
+ unsigned width, height, terrain, red, green, blue;
+ unsigned char found, mask, cityorcounty;
+ int indx, x, y, z=1, x0, y0, dBm, level, hundreds,
+ tens, units, match, colorwidth;
+ double conversion, one_over_gamma, lat, lon,
+ north, south, east, west, minwest;
+ FILE *fd;
+
+ one_over_gamma=1.0/GAMMA;
+ conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma);
+
+ width=(unsigned)(ippd*ReduceAngle(max_west-min_west));
+ height=(unsigned)(ippd*ReduceAngle(max_north-min_north));
+
+ LoadDBMColors(xmtr[0]);
+
+ if (filename[0]==0)
+ {
+ strncpy(filename, xmtr[0].filename,254);
+ filename[strlen(filename)-4]=0; /* Remove .qth */
+ }
+
+ y=strlen(filename);
+
+ if (y>4)
+ {
+ if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.')
+ y-=4;
+ }
+
+ for (x=0; x<y; x++)
+ {
+ mapfile[x]=filename[x];
+ geofile[x]=filename[x];
+ kmlfile[x]=filename[x];
+ }
+
+ mapfile[x]='.';
+ geofile[x]='.';
+ kmlfile[x]='.';
+ mapfile[x+1]='p';
+ geofile[x+1]='g';
+ kmlfile[x+1]='k';
+ mapfile[x+2]='p';
+ geofile[x+2]='e';
+ kmlfile[x+2]='m';
+ mapfile[x+3]='m';
+ geofile[x+3]='o';
+ kmlfile[x+3]='l';
+ mapfile[x+4]=0;
+ geofile[x+4]=0;
+ kmlfile[x+4]=0;
+
+ minwest=((double)min_west)+dpp;
+
+ if (minwest>360.0)
+ minwest-=360.0;
+
+ north=(double)max_north-dpp;
+
+ if (kml || geo)
+ south=(double)min_north; /* No bottom legend */
+ else
+ south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */
+
+ east=(minwest<180.0?-minwest:360.0-min_west);
+ west=(double)(max_west<180?-max_west:360-max_west);
+
+ if (geo && kml==0)
+ {
+ fd=fopen(geofile,"wb");
+
+ fprintf(fd,"FILENAME\t%s\n",mapfile);
+ fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n");
+ fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north);
+
+ fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south);
+ fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height);
+
+ fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version);
+
+ fclose(fd);
+ }
+
+ if (kml && geo==0)
+ {
+ fd=fopen(kmlfile,"wb");
+
+ fprintf(fd,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ fprintf(fd,"<kml xmlns=\"http://earth.google.com/kml/2.1\">\n");
+ fprintf(fd,"<!-- Generated by %s Version %s -->\n",splat_name,splat_version);
+ fprintf(fd," <Folder>\n");
+ fprintf(fd," <name>%s</name>\n",splat_name);
+ fprintf(fd," <description>%s Transmitter Coverage Overlay</description>\n",xmtr[0].name);
+ fprintf(fd," <GroundOverlay>\n");
+ fprintf(fd," <name>SPLAT! Signal Power Level Overlay</name>\n");
+ fprintf(fd," <description>SPLAT! Coverage</description>\n");
+ fprintf(fd," <Icon>\n");
+ fprintf(fd," <href>%s</href>\n",mapfile);
+ fprintf(fd," </Icon>\n");
+ fprintf(fd," <opacity>128</opacity>\n");
+ fprintf(fd," <LatLonBox>\n");
+ fprintf(fd," <north>%.5f</north>\n",north);
+ fprintf(fd," <south>%.5f</south>\n",south);
+ fprintf(fd," <east>%.5f</east>\n",east);
+ fprintf(fd," <west>%.5f</west>\n",west);
+ fprintf(fd," <rotation>0.0</rotation>\n");
+ fprintf(fd," </LatLonBox>\n");
+ fprintf(fd," </GroundOverlay>\n");
+
+ for (x=0; x<txsites; x++)
+ {
+ fprintf(fd," <Placemark>\n");
+ fprintf(fd," <name>%s</name>\n",xmtr[x].name);
+ fprintf(fd," <visibility>1</visibility>\n");
+ fprintf(fd," <Style>\n");
+ fprintf(fd," <IconStyle>\n");
+ fprintf(fd," <Icon>\n");
+ fprintf(fd," <href>root://icons/palette-5.png</href>\n");
+ fprintf(fd," <x>224</x>\n");
+ fprintf(fd," <y>224</y>\n");
+ fprintf(fd," <w>32</w>\n");
+ fprintf(fd," <h>32</h>\n");
+ fprintf(fd," </Icon>\n");
+ fprintf(fd," </IconStyle>\n");
+ fprintf(fd," </Style>\n");
+ fprintf(fd," <Point>\n");
+ fprintf(fd," <extrude>1</extrude>\n");
+ fprintf(fd," <altitudeMode>relativeToGround</altitudeMode>\n");
+ 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);
+ fprintf(fd," </Point>\n");
+ fprintf(fd," </Placemark>\n");
+ }
+
+ fprintf(fd," </Folder>\n");
+ fprintf(fd,"</kml>\n");
+
+ fclose(fd);
+ }
+
+ fd=fopen(mapfile,"wb");
+
+ fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30));
+ fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30));
+ fflush(stdout);
+
+ for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y))
+ {
+ for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x))
+ {
+ if (lon<0.0)
+ lon+=360.0;
+
+ for (indx=0, found=0; indx<MAXPAGES && found==0;)
+ {
+ x0=(int)rint(ppd*(lat-(double)dem[indx].min_north));
+ y0=mpi-(int)rint(ppd*(LonDiff((double)dem[indx].max_west,lon)));
+
+ if (x0>=0 && x0<=mpi && y0>=0 && y0<=mpi)
+ found=1;
+ else
+ indx++;
+ }
+
+ if (found)
+ {
+ mask=dem[indx].mask[x0][y0];
+ dBm=(dem[indx].signal[x0][y0])-200;
+ cityorcounty=0;
+
+ match=255;
+
+ red=0;
+ green=0;
+ blue=0;
+
+ if (dBm>=region.level[0])
+ match=0;
+ else
+ {
+ for (z=1; (z<region.levels && match==255); z++)
+ {
+ if (dBm<region.level[z-1] && dBm>=region.level[z])
+ match=z;
+ }
+ }
+
+ if (match<region.levels)
+ {
+ red=region.color[match][0];
+ green=region.color[match][1];
+ blue=region.color[match][2];
+ }
+
+ if (mask&2)
+ {
+ /* Text Labels: Red or otherwise */
+
+ if (red>=180 && green<=75 && blue<=75 && dBm!=0)
+ fprintf(fd,"%c%c%c",255^red,255^green,255^blue);
+ else
+ fprintf(fd,"%c%c%c",255,0,0);
+
+ cityorcounty=1;
+ }
+
+ else if (mask&4)
+ {
+ /* County Boundaries: Black */
+
+ fprintf(fd,"%c%c%c",0,0,0);
+
+ cityorcounty=1;
+ }
+
+ if (cityorcounty==0)
+ {
+ if (contour_threshold!=0 && dBm<contour_threshold)
+ {
+ if (ngs) /* No terrain */
+ fprintf(fd,"%c%c%c",255,255,255);
+ else
+ {
+ /* Display land or sea elevation */
+
+ if (dem[indx].data[x0][y0]==0)
+ fprintf(fd,"%c%c%c",0,0,170);
+ else
+ {
+ terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
+ fprintf(fd,"%c%c%c",terrain,terrain,terrain);
+ }
+ }
+ }
+
+ else
+ {
+ /* Plot signal power level regions in color */
+
+ if (red!=0 || green!=0 || blue!=0)
+ fprintf(fd,"%c%c%c",red,green,blue);
+
+ else /* terrain / sea-level */
+ {
+ if (ngs)
+ fprintf(fd,"%c%c%c",255,255,255);
+ else
+ {
+ if (dem[indx].data[x0][y0]==0)
+ fprintf(fd,"%c%c%c",0,0,170);
+ else
+ {
+ /* Elevation: Greyscale */
+ terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion);
+ fprintf(fd,"%c%c%c",terrain,terrain,terrain);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ else
+ {
+ /* We should never get here, but if */
+ /* we do, display the region as black */
+
+ fprintf(fd,"%c%c%c",0,0,0);
+ }
+ }
+ }
+
+ if (kml==0 && geo==0)
+ {
+ /* Display legend along bottom of image
+ if not generating .kml or .geo output. */
+
+ colorwidth=(int)rint((float)width/(float)region.levels);
+
+ for (y0=0; y0<30; y0++)
+ {
+ for (x0=0; x0<(int)width; x0++)
+ {
+ indx=x0/colorwidth;
+ x=x0%colorwidth;
+
+ level=abs(region.level[indx]);
+
+ hundreds=level/100;
+
+ if (hundreds>0)
+ level-=(hundreds*100);
+
+ tens=level/10;
+
+ if (tens>0)
+ level-=(tens*10);
+
+ units=level;
+
+ if (y0>=8 && y0<=23)
+ {
+ if (hundreds>0)
+ {
+ if (region.level[indx]<0)
+ {
+ if (x>=5 && x<=12)
+ if (fontdata[16*('-')+(y0-8)]&(128>>(x-5)))
+ indx=255;
+ }
+
+ else
+ {
+ if (x>=5 && x<=12)
+ if (fontdata[16*('+')+(y0-8)]&(128>>(x-5)))
+ indx=255;
+ }
+
+ if (x>=13 && x<=20)
+ if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-13)))
+ indx=255;
+ }
+
+ if (tens>0 || hundreds>0)
+ {
+ if (hundreds==0)
+ {
+ if (region.level[indx]<0)
+ {
+ if (x>=13 && x<=20)
+ if (fontdata[16*('-')+(y0-8)]&(128>>(x-13)))
+ indx=255;
+ }
+
+ else
+ {
+ if (x>=13 && x<=20)
+ if (fontdata[16*('+')+(y0-8)]&(128>>(x-13)))
+ indx=255;
+ }
+ }
+
+ if (x>=21 && x<=28)
+ if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-21)))
+ indx=255;
+ }
+
+ if (hundreds==0 && tens==0)
+ {
+ if (region.level[indx]<0)
+ {
+ if (x>=21 && x<=28)
+ if (fontdata[16*('-')+(y0-8)]&(128>>(x-21)))
+ indx=255;
+ }
+
+ else
+ {
+ if (x>=21 && x<=28)
+ if (fontdata[16*('+')+(y0-8)]&(128>>(x-21)))
+ indx=255;
+ }
+ }
+
+ if (x>=29 && x<=36)
+ if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-29)))
+ indx=255;
+
+ if (x>=37 && x<=44)
+ if (fontdata[16*('d')+(y0-8)]&(128>>(x-37)))
+ indx=255;
+
+ if (x>=45 && x<=52)
+ if (fontdata[16*('B')+(y0-8)]&(128>>(x-45)))
+ indx=255;
+
+ if (x>=53 && x<=60)
+ if (fontdata[16*('m')+(y0-8)]&(128>>(x-53)))
+ indx=255;
+ }
+
+ if (indx>region.levels)
+ fprintf(fd,"%c%c%c",0,0,0);
+ else
+ {
+ red=region.color[indx][0];
+ green=region.color[indx][1];
+ blue=region.color[indx][2];
+
+ fprintf(fd,"%c%c%c",red,green,blue);
+ }
+ }
+ }
+ }
+
+ fclose(fd);
+ fprintf(stdout,"Done!\n");
+ fflush(stdout);
+}
+
+void GraphTerrain(struct site source, struct site destination, char *name)
+{
+ /* This function invokes gnuplot to generate an appropriate
+ output file indicating the terrain profile between the source
+ and destination locations when the -p command line option
+ is used. "basename" is the name assigned to the output
+ file generated by gnuplot. The filename extension is used
+ to set gnuplot's terminal setting and output file type.
+ If no extension is found, .png is assumed. */
+
+ int x, y, z;
+ char basename[255], term[30], ext[15];
+ double minheight=100000.0, maxheight=-100000.0;
+ FILE *fd=NULL, *fd1=NULL;
+
+ ReadPath(destination,source);
+
+ fd=fopen("profile.gp","wb");
+
+ if (clutter>0.0)
+ fd1=fopen("clutter.gp","wb");
+
+ for (x=0; x<path.length; x++)
+ {
+ if ((path.elevation[x]+clutter)>maxheight)
+ maxheight=path.elevation[x]+clutter;
+
+ if (path.elevation[x]<minheight)
+ minheight=path.elevation[x];
+
+ if (metric)
+ {
+ fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*path.elevation[x]);
+
+ if (fd1!=NULL && x>0 && x<path.length-2)
+ 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)));
+ }
+
+ else
+ {
+ fprintf(fd,"%f\t%f\n",path.distance[x],path.elevation[x]);
+
+ if (fd1!=NULL && x>0 && x<path.length-2)
+ fprintf(fd1,"%f\t%f\n",path.distance[x],(path.elevation[x]==0.0?path.elevation[x]:(path.elevation[x]+clutter)));
+ }
+ }
+
+ fclose(fd);
+
+ if (fd1!=NULL)
+ fclose(fd1);
+
+ if (name[0]=='.')
+ {
+ /* Default filename and output file type */
+
+ strncpy(basename,"profile\0",8);
+ strncpy(term,"png\0",4);
+ strncpy(ext,"png\0",4);
+ }
+
+ else
+ {
+ /* Extract extension and terminal type from "name" */
+
+ ext[0]=0;
+ y=strlen(name);
+ strncpy(basename,name,254);
+
+ for (x=y-1; x>0 && name[x]!='.'; x--);
+
+ if (x>0) /* Extension found */
+ {
+ for (z=x+1; z<=y && (z-(x+1))<10; z++)
+ {
+ ext[z-(x+1)]=tolower(name[z]);
+ term[z-(x+1)]=name[z];
+ }
+
+ ext[z-(x+1)]=0; /* Ensure an ending 0 */
+ term[z-(x+1)]=0;
+ basename[x]=0;
+ }
+
+ if (ext[0]==0) /* No extension -- Default is png */
+ {
+ strncpy(term,"png\0",4);
+ strncpy(ext,"png\0",4);
+ }
+ }
+
+ /* Either .ps or .postscript may be used
+ as an extension for postscript output. */
+
+ if (strncmp(term,"postscript",10)==0)
+ strncpy(ext,"ps\0",3);
+
+ else if (strncmp(ext,"ps",2)==0)
+ strncpy(term,"postscript enhanced color\0",26);
+
+ minheight-=(0.01*maxheight);
+
+ fd=fopen("splat.gp","w");
+ fprintf(fd,"set grid\n");
+ fprintf(fd,"set yrange [%2.3f to %2.3f]\n", metric?minheight*METERS_PER_FOOT:minheight, metric?maxheight*METERS_PER_FOOT:maxheight);
+ fprintf(fd,"set encoding iso_8859_1\n");
+ fprintf(fd,"set term %s\n",term);
+ 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);
+
+ if (metric)
+ {
+ fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination));
+ fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (meters)\"\n");
+ }
+
+ else
+ {
+ fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination));
+ fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (feet)\"\n");
+ }
+
+ fprintf(fd,"set output \"%s.%s\"\n",basename,ext);
+
+ if (clutter>0.0)
+ {
+ if (metric)
+ fprintf(fd,"plot \"profile.gp\" title \"Terrain Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f meters)\" with lines\n",clutter*METERS_PER_FOOT);
+ else
+ fprintf(fd,"plot \"profile.gp\" title \"Terrain Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f feet)\" with lines\n",clutter);
+ }
+
+ else
+ fprintf(fd,"plot \"profile.gp\" title \"\" with lines\n");
+
+ fclose(fd);
+
+ x=system("gnuplot splat.gp");
+
+ if (x!=-1)
+ {
+ if (gpsav==0)
+ {
+ unlink("splat.gp");
+ unlink("profile.gp");
+ }
+
+ fprintf(stdout,"Terrain plot written to: \"%s.%s\"\n",basename,ext);
+ fflush(stdout);
+ }
+
+ else
+ fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
+}
+
+void GraphElevation(struct site source, struct site destination, char *name)
+{
+ /* This function invokes gnuplot to generate an appropriate
+ output file indicating the terrain elevation profile between
+ the source and destination locations when the -e command line
+ option is used. "basename" is the name assigned to the output
+ file generated by gnuplot. The filename extension is used
+ to set gnuplot's terminal setting and output file type.
+ If no extension is found, .png is assumed. */
+
+ int x, y, z;
+ char basename[255], term[30], ext[15];
+ double angle, clutter_angle=0.0, refangle, maxangle=-90.0,
+ minangle=90.0, distance;
+ struct site remote, remote2;
+ FILE *fd=NULL, *fd1=NULL, *fd2=NULL;
+
+ ReadPath(destination,source); /* destination=RX, source=TX */
+ refangle=ElevationAngle(destination,source);
+ distance=Distance(source,destination);
+
+ fd=fopen("profile.gp","wb");
+
+ if (clutter>0.0)
+ fd1=fopen("clutter.gp","wb");
+
+ fd2=fopen("reference.gp","wb");
+
+ for (x=1; x<path.length-1; x++)
+ {
+ remote.lat=path.lat[x];
+ remote.lon=path.lon[x];
+ remote.alt=0.0;
+ angle=ElevationAngle(destination,remote);
+
+ if (clutter>0.0)
+ {
+ remote2.lat=path.lat[x];
+ remote2.lon=path.lon[x];
+
+ if (path.elevation[x]!=0.0)
+ remote2.alt=clutter;
+ else
+ remote2.alt=0.0;
+
+ clutter_angle=ElevationAngle(destination,remote2);
+ }
+
+ if (metric)
+ {
+ fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],angle);
+
+ if (fd1!=NULL)
+ fprintf(fd1,"%f\t%f\n",KM_PER_MILE*path.distance[x],clutter_angle);
+
+ fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[x],refangle);
+ }
+
+ else
+ {
+ fprintf(fd,"%f\t%f\n",path.distance[x],angle);
+
+ if (fd1!=NULL)
+ fprintf(fd1,"%f\t%f\n",path.distance[x],clutter_angle);
+
+ fprintf(fd2,"%f\t%f\n",path.distance[x],refangle);
+ }
+
+ if (angle>maxangle)
+ maxangle=angle;
+
+ if (clutter_angle>maxangle)
+ maxangle=clutter_angle;
+
+ if (angle<minangle)
+ minangle=angle;
+ }
+
+ if (metric)
+ {
+ fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],refangle);
+ fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],refangle);
+ }
+
+ else
+ {
+ fprintf(fd,"%f\t%f\n",path.distance[path.length-1],refangle);
+ fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],refangle);
+ }
+
+ fclose(fd);
+
+ if (fd1!=NULL)
+ fclose(fd1);
+
+ fclose(fd2);
+
+ if (name[0]=='.')
+ {
+ /* Default filename and output file type */
+
+ strncpy(basename,"profile\0",8);
+ strncpy(term,"png\0",4);
+ strncpy(ext,"png\0",4);
+ }
+
+ else
+ {
+ /* Extract extension and terminal type from "name" */
+
+ ext[0]=0;
+ y=strlen(name);
+ strncpy(basename,name,254);
+
+ for (x=y-1; x>0 && name[x]!='.'; x--);
+
+ if (x>0) /* Extension found */
+ {
+ for (z=x+1; z<=y && (z-(x+1))<10; z++)
+ {
+ ext[z-(x+1)]=tolower(name[z]);
+ term[z-(x+1)]=name[z];
+ }
+
+ ext[z-(x+1)]=0; /* Ensure an ending 0 */
+ term[z-(x+1)]=0;
+ basename[x]=0;
+ }
+
+ if (ext[0]==0) /* No extension -- Default is png */
+ {
+ strncpy(term,"png\0",4);
+ strncpy(ext,"png\0",4);
+ }
+ }
+
+ /* Either .ps or .postscript may be used
+ as an extension for postscript output. */
+
+ if (strncmp(term,"postscript",10)==0)
+ strncpy(ext,"ps\0",3);
+
+ else if (strncmp(ext,"ps",2)==0)
+ strncpy(term,"postscript enhanced color\0",26);
+
+ fd=fopen("splat.gp","w");
+
+ fprintf(fd,"set grid\n");
+
+ if (distance>2.0)
+ fprintf(fd,"set yrange [%2.3f to %2.3f]\n", (-fabs(refangle)-0.25), maxangle+0.25);
+ else
+ fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minangle, refangle+(-minangle/8.0));
+
+ fprintf(fd,"set encoding iso_8859_1\n");
+ fprintf(fd,"set term %s\n",term);
+ 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);
+
+ if (metric)
+ fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*distance);
+ else
+ fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,distance);
+
+
+ fprintf(fd,"set ylabel \"Elevation Angle Along LOS Path Between\\n%s and %s (degrees)\"\n",destination.name,source.name);
+ fprintf(fd,"set output \"%s.%s\"\n",basename,ext);
+
+ if (clutter>0.0)
+ {
+ if (metric)
+ 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);
+ else
+ 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);
+ }
+
+ else
+ 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);
+
+ fclose(fd);
+
+ x=system("gnuplot splat.gp");
+
+ if (x!=-1)
+ {
+ if (gpsav==0)
+ {
+ unlink("splat.gp");
+ unlink("profile.gp");
+ unlink("reference.gp");
+
+ if (clutter>0.0)
+ unlink("clutter.gp");
+ }
+
+ fprintf(stdout,"Elevation plot written to: \"%s.%s\"\n",basename,ext);
+ fflush(stdout);
+ }
+
+ else
+ fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
+}
+
+void GraphHeight(struct site source, struct site destination, char *name, unsigned char fresnel_plot, unsigned char normalized)
+{
+ /* This function invokes gnuplot to generate an appropriate
+ output file indicating the terrain height profile between
+ the source and destination locations referenced to the
+ line-of-sight path between the receive and transmit sites
+ when the -h or -H command line option is used. "basename"
+ is the name assigned to the output file generated by gnuplot.
+ The filename extension is used to set gnuplot's terminal
+ setting and output file type. If no extension is found,
+ .png is assumed. */
+
+ int x, y, z;
+ char basename[255], term[30], ext[15];
+ double a, b, c, height=0.0, refangle, cangle, maxheight=-100000.0,
+ minheight=100000.0, lambda=0.0, f_zone=0.0, fpt6_zone=0.0,
+ nm=0.0, nb=0.0, ed=0.0, es=0.0, r=0.0, d=0.0, d1=0.0,
+ terrain, azimuth, distance, dheight=0.0, minterrain=100000.0,
+ minearth=100000.0, miny, maxy, min2y, max2y;
+ struct site remote;
+ FILE *fd=NULL, *fd1=NULL, *fd2=NULL, *fd3=NULL, *fd4=NULL, *fd5=NULL;
+
+ ReadPath(destination,source); /* destination=RX, source=TX */
+ azimuth=Azimuth(destination,source);
+ distance=Distance(destination,source);
+ refangle=ElevationAngle(destination,source);
+ b=GetElevation(destination)+destination.alt+earthradius;
+
+ /* Wavelength and path distance (great circle) in feet. */
+
+ if (fresnel_plot)
+ {
+ lambda=9.8425e8/(LR.frq_mhz*1e6);
+ d=5280.0*path.distance[path.length-1];
+ }
+
+ if (normalized)
+ {
+ ed=GetElevation(destination);
+ es=GetElevation(source);
+ nb=-destination.alt-ed;
+ nm=(-source.alt-es-nb)/(path.distance[path.length-1]);
+ }
+
+ fd=fopen("profile.gp","wb");
+
+ if (clutter>0.0)
+ fd1=fopen("clutter.gp","wb");
+
+ fd2=fopen("reference.gp","wb");
+ fd5=fopen("curvature.gp", "wb");
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ fd3=fopen("fresnel.gp", "wb");
+ fd4=fopen("fresnel_pt_6.gp", "wb");
+ }
+
+ for (x=0; x<path.length-1; x++)
+ {
+ remote.lat=path.lat[x];
+ remote.lon=path.lon[x];
+ remote.alt=0.0;
+
+ terrain=GetElevation(remote);
+
+ if (x==0)
+ terrain+=destination.alt; /* RX antenna spike */
+
+ a=terrain+earthradius;
+ cangle=5280.0*Distance(destination,remote)/earthradius;
+ c=b*sin(refangle*DEG2RAD+HALFPI)/sin(HALFPI-refangle*DEG2RAD-cangle);
+
+ height=a-c;
+
+ /* Per Fink and Christiansen, Electronics
+ * Engineers' Handbook, 1989:
+ *
+ * H = sqrt(lamba * d1 * (d - d1)/d)
+ *
+ * where H is the distance from the LOS
+ * path to the first Fresnel zone boundary.
+ */
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ d1=5280.0*path.distance[x];
+ f_zone=-1.0*sqrt(lambda*d1*(d-d1)/d);
+ fpt6_zone=f_zone*fzone_clearance;
+ }
+
+ if (normalized)
+ {
+ r=-(nm*path.distance[x])-nb;
+ height+=r;
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ f_zone+=r;
+ fpt6_zone+=r;
+ }
+ }
+
+ else
+ r=0.0;
+
+ if (metric)
+ {
+ fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*height);
+
+ if (fd1!=NULL && x>0 && x<path.length-2)
+ fprintf(fd1,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*(terrain==0.0?height:(height+clutter)));
+
+ fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*r);
+ fprintf(fd5,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*(height-terrain));
+ }
+
+ else
+ {
+ fprintf(fd,"%f\t%f\n",path.distance[x],height);
+
+ if (fd1!=NULL && x>0 && x<path.length-2)
+ fprintf(fd1,"%f\t%f\n",path.distance[x],(terrain==0.0?height:(height+clutter)));
+
+ fprintf(fd2,"%f\t%f\n",path.distance[x],r);
+ fprintf(fd5,"%f\t%f\n",path.distance[x],height-terrain);
+ }
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ if (metric)
+ {
+ fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*f_zone);
+ fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*fpt6_zone);
+ }
+
+ else
+ {
+ fprintf(fd3,"%f\t%f\n",path.distance[x],f_zone);
+ fprintf(fd4,"%f\t%f\n",path.distance[x],fpt6_zone);
+ }
+
+ if (f_zone<minheight)
+ minheight=f_zone;
+ }
+
+ if ((height+clutter)>maxheight)
+ maxheight=height+clutter;
+
+ if (height<minheight)
+ minheight=height;
+
+ if (r>maxheight)
+ maxheight=r;
+
+ if (terrain<minterrain)
+ minterrain=terrain;
+
+ if ((height-terrain)<minearth)
+ minearth=height-terrain;
+ }
+
+ if (normalized)
+ r=-(nm*path.distance[path.length-1])-nb;
+ else
+ r=0.0;
+
+ if (metric)
+ {
+ fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
+ fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
+ }
+
+ else
+ {
+ fprintf(fd,"%f\t%f\n",path.distance[path.length-1],r);
+ fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],r);
+ }
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ if (metric)
+ {
+ fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
+ fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r);
+ }
+
+ else
+ {
+ fprintf(fd3,"%f\t%f\n",path.distance[path.length-1],r);
+ fprintf(fd4,"%f\t%f\n",path.distance[path.length-1],r);
+ }
+ }
+
+ if (r>maxheight)
+ maxheight=r;
+
+ if (r<minheight)
+ minheight=r;
+
+ fclose(fd);
+
+ if (fd1!=NULL)
+ fclose(fd1);
+
+ fclose(fd2);
+ fclose(fd5);
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ fclose(fd3);
+ fclose(fd4);
+ }
+
+ if (name[0]=='.')
+ {
+ /* Default filename and output file type */
+
+ strncpy(basename,"profile\0",8);
+ strncpy(term,"png\0",4);
+ strncpy(ext,"png\0",4);
+ }
+
+ else
+ {
+ /* Extract extension and terminal type from "name" */
+
+ ext[0]=0;
+ y=strlen(name);
+ strncpy(basename,name,254);
+
+ for (x=y-1; x>0 && name[x]!='.'; x--);
+
+ if (x>0) /* Extension found */
+ {
+ for (z=x+1; z<=y && (z-(x+1))<10; z++)
+ {
+ ext[z-(x+1)]=tolower(name[z]);
+ term[z-(x+1)]=name[z];
+ }
+
+ ext[z-(x+1)]=0; /* Ensure an ending 0 */
+ term[z-(x+1)]=0;
+ basename[x]=0;
+ }
+
+ if (ext[0]==0) /* No extension -- Default is png */
+ {
+ strncpy(term,"png\0",4);
+ strncpy(ext,"png\0",4);
+ }
+ }
+
+ /* Either .ps or .postscript may be used
+ as an extension for postscript output. */
+
+ if (strncmp(term,"postscript",10)==0)
+ strncpy(ext,"ps\0",3);
+
+ else if (strncmp(ext,"ps",2)==0)
+ strncpy(term,"postscript enhanced color\0",26);
+
+ fd=fopen("splat.gp","w");
+
+ dheight=maxheight-minheight;
+ miny=minheight-0.15*dheight;
+ maxy=maxheight+0.05*dheight;
+
+ if (maxy<20.0)
+ maxy=20.0;
+
+ dheight=maxheight-minheight;
+ min2y=miny-minterrain+0.05*dheight;
+
+ if (minearth<min2y)
+ {
+ miny-=min2y-minearth+0.05*dheight;
+ min2y=minearth-0.05*dheight;
+ }
+
+ max2y=min2y+maxy-miny;
+
+ fprintf(fd,"set grid\n");
+ fprintf(fd,"set yrange [%2.3f to %2.3f]\n", metric?miny*METERS_PER_FOOT:miny, metric?maxy*METERS_PER_FOOT:maxy);
+ fprintf(fd,"set y2range [%2.3f to %2.3f]\n", metric?min2y*METERS_PER_FOOT:min2y, metric?max2y*METERS_PER_FOOT:max2y);
+ fprintf(fd,"set xrange [-0.5 to %2.3f]\n",metric?KM_PER_MILE*rint(distance+0.5):rint(distance+0.5));
+ fprintf(fd,"set encoding iso_8859_1\n");
+ fprintf(fd,"set term %s\n",term);
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ 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);
+
+ else
+ fprintf(fd,"set title \"%s Height Profile Between %s and %s (%.2f%c azimuth)\"\n",splat_name, destination.name, source.name, azimuth,176);
+
+ if (metric)
+ fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination));
+ else
+ fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination));
+
+ if (normalized)
+ {
+ if (metric)
+ fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (meters)\"\n",destination.name,source.name);
+
+ else
+ fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (feet)\"\n",destination.name,source.name);
+
+ }
+
+ else
+ {
+ if (metric)
+ fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between\\n%s and %s (meters)\"\n",destination.name,source.name);
+
+ else
+ fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between\\n%s and %s (feet)\"\n",destination.name,source.name);
+ }
+
+ fprintf(fd,"set output \"%s.%s\"\n",basename,ext);
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ if (clutter>0.0)
+ {
+ if (metric)
+ 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);
+ else
+ 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);
+ }
+
+ else
+ 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);
+ }
+
+ else
+ {
+ if (clutter>0.0)
+ {
+ if (metric)
+ 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);
+ else
+ 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);
+ }
+
+ else
+ 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");
+
+ }
+
+ fclose(fd);
+
+ x=system("gnuplot splat.gp");
+
+ if (x!=-1)
+ {
+ if (gpsav==0)
+ {
+ unlink("splat.gp");
+ unlink("profile.gp");
+ unlink("reference.gp");
+ unlink("curvature.gp");
+
+ if (fd1!=NULL)
+ unlink("clutter.gp");
+
+ if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot)
+ {
+ unlink("fresnel.gp");
+ unlink("fresnel_pt_6.gp");
+ }
+ }
+
+ fprintf(stdout,"\nHeight plot written to: \"%s.%s\"",basename,ext);
+ fflush(stdout);
+ }
+
+ else
+ fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n");
+}
+
+void ObstructionAnalysis(struct site xmtr, struct site rcvr, double f, FILE *outfile)
+{
+ /* Perform an obstruction analysis along the
+ path between receiver and transmitter. */
+
+ int x;
+ struct site site_x;
+ double h_r, h_t, h_x, h_r_orig, cos_tx_angle, cos_test_angle,
+ cos_tx_angle_f1, cos_tx_angle_fpt6, d_tx, d_x,
+ h_r_f1, h_r_fpt6, h_f, h_los, lambda=0.0;
+ char string[255], string_fpt6[255], string_f1[255];
+
+ ReadPath(xmtr,rcvr);
+ h_r=GetElevation(rcvr)+rcvr.alt+earthradius;
+ h_r_f1=h_r;
+ h_r_fpt6=h_r;
+ h_r_orig=h_r;
+ h_t=GetElevation(xmtr)+xmtr.alt+earthradius;
+ d_tx=5280.0*Distance(rcvr,xmtr);
+ cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx);
+ cos_tx_angle_f1=cos_tx_angle;
+ cos_tx_angle_fpt6=cos_tx_angle;
+
+ if (f)
+ lambda=9.8425e8/(f*1e6);
+
+ if (clutter>0.0)
+ {
+ fprintf(outfile,"Terrain has been raised by");
+
+ if (metric)
+ fprintf(outfile," %.2f meters",METERS_PER_FOOT*clutter);
+ else
+ fprintf(outfile," %.2f feet",clutter);
+
+ fprintf(outfile," to account for ground clutter.\n\n");
+ }
+
+ /* At each point along the path calculate the cosine
+ of a sort of "inverse elevation angle" at the receiver.
+ From the antenna, 0 deg. looks at the ground, and 90 deg.
+ is parallel to the ground.
+
+ Start at the receiver. If this is the lowest antenna,
+ then terrain obstructions will be nearest to it. (Plus,
+ that's the way SPLAT!'s original los() did it.)
+
+ Calculate cosines only. That's sufficient to compare
+ angles and it saves the extra computational burden of
+ acos(). However, note the inverted comparison: if
+ acos(A) > acos(B), then B > A. */
+
+ for (x=path.length-1; x>0; x--)
+ {
+ site_x.lat=path.lat[x];
+ site_x.lon=path.lon[x];
+ site_x.alt=0.0;
+
+ h_x=GetElevation(site_x)+earthradius+clutter;
+ d_x=5280.0*Distance(rcvr,site_x);
+
+ /* Deal with the LOS path first. */
+
+ cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x);
+
+ if (cos_tx_angle>cos_test_angle)
+ {
+ if (h_r==h_r_orig)
+ fprintf(outfile,"Between %s and %s, %s detected obstructions at:\n\n",rcvr.name,xmtr.name,splat_name);
+
+ if (site_x.lat>=0.0)
+ {
+ if (metric)
+ 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));
+ else
+ 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);
+ }
+
+ else
+ {
+ if (metric)
+ 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));
+ else
+
+ 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);
+ }
+ }
+
+ while (cos_tx_angle>cos_test_angle)
+ {
+ h_r+=1;
+ cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x);
+ cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx);
+ }
+
+ if (f)
+ {
+ /* Now clear the first Fresnel zone... */
+
+ 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);
+ h_los=sqrt(h_r_f1*h_r_f1+d_x*d_x-2*h_r_f1*d_x*cos_tx_angle_f1);
+ h_f=h_los-sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
+
+ while (h_f<h_x)
+ {
+ h_r_f1+=1;
+ 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);
+ h_los=sqrt(h_r_f1*h_r_f1+d_x*d_x-2*h_r_f1*d_x*cos_tx_angle_f1);
+ h_f=h_los-sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
+ }
+
+ /* and clear the 60% F1 zone. */
+
+ 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);
+ h_los=sqrt(h_r_fpt6*h_r_fpt6+d_x*d_x-2*h_r_fpt6*d_x*cos_tx_angle_fpt6);
+ h_f=h_los-fzone_clearance*sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
+
+ while (h_f<h_x)
+ {
+ h_r_fpt6+=1;
+ 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);
+ h_los=sqrt(h_r_fpt6*h_r_fpt6+d_x*d_x-2*h_r_fpt6*d_x*cos_tx_angle_fpt6);
+ h_f=h_los-fzone_clearance*sqrt(lambda*d_x*(d_tx-d_x)/d_tx);
+ }
+ }
+ }
+
+ if (h_r>h_r_orig)
+ {
+ if (metric)
+ 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);
+ else
+ 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);
+ }
+
+ else
+ snprintf(string,150,"\nNo obstructions to LOS path due to terrain were detected by %s\n",splat_name);
+
+ if (f)
+ {
+ if (h_r_fpt6>h_r_orig)
+ {
+ if (metric)
+ 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);
+
+ else
+ 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);
+ }
+
+ else
+ snprintf(string_fpt6,150,"\n%.0f%c of the first Fresnel zone is clear.\n",fzone_clearance*100.0,37);
+
+ if (h_r_f1>h_r_orig)
+ {
+ if (metric)
+ 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));
+
+ else
+ 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);
+
+ }
+
+ else
+ snprintf(string_f1,150,"\nThe first Fresnel zone is clear.\n");
+ }
+
+ fprintf(outfile,"%s",string);
+
+ if (f)
+ {
+ fprintf(outfile,"%s",string_f1);
+ fprintf(outfile,"%s",string_fpt6);
+ }
+}