X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=splat.cpp;h=8aee28727c97952847e128a904a5205a07d48c13;hb=2b83ee9944726f29b3f6679fba24a902bcd33c56;hp=7724fc69fb0f6631eaac793e3b06c59b43073ac7;hpb=8c97af274a0a0c0f6c949aab5b35731f7b187873;p=debian%2Fsplat diff --git a/splat.cpp b/splat.cpp index 7724fc6..8aee287 100644 --- a/splat.cpp +++ b/splat.cpp @@ -1,9 +1,17 @@ /**************************************************************************** -* SPLAT: A Terrain Analysis Program * -* Copyright John A. Magliacane, KD2BD 1997-2004 * -* Last update: 24-Jan-2004 * +* SPLAT: An RF Signal Propagation Loss and Terrain Analysis Tool * +* Last update: 15-Mar-2007 * ***************************************************************************** +* Project started in 1997 by John A. Magliacane, KD2BD * +***************************************************************************** +* * +* Extensively modified by J. D. McDonald in Jan. 2004 to include * +* the Longley-Rice propagation model using C++ code from NTIA/ITS. * * * +* See: http://flattop.its.bldrdoc.gov/itm.html * +* * +***************************************************************************** +* * * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation; either version 2 of the License or any later * @@ -14,13 +22,6 @@ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * -***************************************************************************** -* * -* Extensively modified by J. D. McDonald in Jan. 2004 to include * -* the Longley-Rice propagation model using C++ code from NTIA/ITS. * -* * -* See: http://elbert.its.bldrdoc.gov/itm.html * -* * ***************************************************************************** g++ -Wall -O3 -s -lm -lbz2 -fomit-frame-pointer itm.cpp splat.cpp -o splat *****************************************************************************/ @@ -55,49 +56,52 @@ #define ARRAYSIZE 30025 #endif -char string[255], sdf_path[255], opened=0, *splat_version={"1.1.0"}; +char string[255], sdf_path[255], opened=0, *splat_version={"1.2.0b"}; double TWOPI=6.283185307179586, HALFPI=1.570796326794896, PI=3.141592653589793, deg2rad=1.74532925199e-02, EARTHRADIUS=20902230.97, METERS_PER_MILE=1609.344, - METERS_PER_FOOT=0.3048, earthradius, max_range=0.0; - -int min_north=0, max_north=0, min_west=0, max_west=0, - max_elevation=0, min_elevation=0, bzerror; - -struct site { double lat; - double lon; - double alt; - char name[50]; - }; - -struct { float lat[ARRAYSIZE]; - float lon[ARRAYSIZE]; - float elevation[ARRAYSIZE]; - float distance[ARRAYSIZE]; - int length; - } path; - -static struct { int min_north; - int max_north; - int min_west; - int max_west; - int max_el; - int min_el; - short data[1200][1200]; - unsigned char mask[1200][1200]; - } dem[MAXSLOTS]; - -struct { - double eps_dielect; - double sgm_conductivity; - double eno_ns_surfref; - double frq_mhz; - double conf; - double rel; - int radio_climate; - int pol; - } LR; + METERS_PER_FOOT=0.3048, KM_PER_MILE=1.609344, earthradius, + max_range=0.0; + +int min_north=90, max_north=-90, min_west=360, max_west=-1, + max_elevation=-32768, min_elevation=32768, bzerror, maxdB=230; + +unsigned char got_elevation_pattern=0, got_azimuth_pattern=0, metric=0; + +struct site { double lat; + double lon; + float alt; + char name[50]; + } site; + +struct path { double lat[ARRAYSIZE]; + double lon[ARRAYSIZE]; + double elevation[ARRAYSIZE]; + double distance[ARRAYSIZE]; + int length; + } path; + +struct dem { int min_north; + int max_north; + int min_west; + int max_west; + int max_el; + int min_el; + short data[1200][1200]; + unsigned char mask[1200][1200]; + } dem[MAXSLOTS]; + +struct LR { double eps_dielect; + double sgm_conductivity; + double eno_ns_surfref; + double frq_mhz; + double conf; + double rel; + int radio_climate; + int pol; + float antenna_pattern[361][1001]; + } LR; double elev_l[ARRAYSIZE+10]; @@ -122,13 +126,35 @@ double arccos(double x, double y) return result; } +int ReduceAngle(double angle) +{ + /* This function normalizes the argument to + an integer angle between 0 and 180 degrees */ + + double temp; + + temp=acos(cos(angle*deg2rad)); + + return (int)rint(temp/deg2rad); +} + char *dec2dms(double decimal) { /* Converts decimal degrees to degrees, minutes, seconds, (DMS) and returns the result as a character string. */ - - int degrees, minutes, seconds; - double a, b, c, d; + + char sign; + int degrees, minutes, seconds; + double a, b, c, d; + + if (decimal<0.0) + { + decimal=-decimal; + sign=-1; + } + + else + sign=1; a=floor(decimal); b=60.0*(decimal-a); @@ -146,7 +172,7 @@ char *dec2dms(double decimal) seconds=59; string[0]=0; - sprintf(string,"%d%c %d\' %d\"", degrees, 176, minutes, seconds); + sprintf(string,"%d%c %d\' %d\"", degrees*sign, 176, minutes, seconds); return (string); } @@ -158,8 +184,8 @@ int OrMask(double lat, double lon, int value) the mask based on the latitude and longitude of the area pointed to. */ - int x, y, indx, minlat, minlon; - char found; + int x, y, indx, minlat, minlon; + char found; minlat=(int)floor(lat); minlon=(int)floor(lon); @@ -176,8 +202,10 @@ int OrMask(double lat, double lon, int value) y=(int)(1199.0*(lon-floor(lon))); dem[indx].mask[x][y]|=value; + return (dem[indx].mask[x][y]); } + else return -1; } @@ -196,9 +224,9 @@ double GetElevation(struct site location) represented by the digital elevation model data in memory. Function returns -5000.0 for locations not found in memory. */ - char found; - int x, y, indx, minlat, minlon; - double elevation; + char found; + int x, y, indx, minlat, minlon; + double elevation; elevation=-5000.0; @@ -212,6 +240,7 @@ double GetElevation(struct site location) { if (minlat==dem[indx].min_north && minlon==dem[indx].min_west) { + elevation=3.28084*dem[indx].data[x][y]; found=1; } @@ -220,12 +249,41 @@ double GetElevation(struct site location) return elevation; } +int AddElevation(double lat, double lon, double height) +{ + /* This function adds a user-defined terrain feature + (in meters AGL) to the digital elevation model data + in memory. Does nothing and returns 0 for locations + not found in memory. */ + + char found; + int x, y, indx, minlat, minlon; + + minlat=(int)floor(lat); + minlon=(int)floor(lon); + + x=(int)(1199.0*(lat-floor(lat))); + y=(int)(1199.0*(lon-floor(lon))); + + for (indx=0, found=0; indxcos_test_angle) + { + block=1; + first_obstruction_angle=((acos(cos_test_angle))/deg2rad)-90.0; + } + } + + if (block) + elevation=first_obstruction_angle; - path.lat[c]=destination.lat; - path.lon[c]=destination.lon; - path.elevation[c]=GetElevation(destination); - path.distance[c]=total_distance; - c++; + else + elevation=((acos(cos_xmtr_angle))/deg2rad)-90.0; + + path=temp; - path.length=c; + return elevation; } double AverageTerrain(struct site source, double azimuthx, double start_distance, double end_distance) @@ -399,9 +540,9 @@ double AverageTerrain(struct site source, double azimuthx, double start_distance memory to complete the survey (critical error), then -9999.0 is returned. */ - int c, samples, endpoint; - double beta, lat1, lon1, lat2, lon2, num, den, azimuth, terrain=0.0; - struct site destination; + int c, samples, endpoint; + double beta, lat1, lon1, lat2, lon2, num, den, azimuth, terrain=0.0; + struct site destination; lat1=source.lat*deg2rad; lon1=source.lon*deg2rad; @@ -490,9 +631,9 @@ double haat(struct site antenna) error occurs, such as a lack of SDF data to complete the survey, -5000.0 is returned. */ - int azi, c; - char error=0; - double terrain, avg_terrain, haat, sum=0.0; + int azi, c; + char error=0; + double terrain, avg_terrain, haat, sum=0.0; /* Calculate the average terrain between 2 and 10 miles from the antenna site at azimuths of 0, 45, 90, 135, @@ -522,6 +663,27 @@ double haat(struct site antenna) } } +float LonDiff(float lon1, float lon2) +{ + /* This function returns the short path longitudinal + difference between longitude1 and longitude2 + as an angle between -180.0 and +180.0 degrees. + If lon1 is west of lon2, the result is positive. + If lon1 is east of lon2, the result is negative. */ + + float diff; + + diff=lon1-lon2; + + if (diff<=-180.0) + diff+=360.0; + + if (diff>=180.0) + diff-=360.0; + + return diff; +} + void PlaceMarker(struct site location) { /* This function places text and marker data in the mask array @@ -546,7 +708,7 @@ void PlaceMarker(struct site location) lat=location.lat; lon=location.lon; - if (latxmin && lonymin) + if (latxmin && (LonDiff(lon,ymax)<0.0) && (LonDiff(lon,ymin)>0.0)) { p1=1.0/1200.0; p3=3.0/1200.0; @@ -561,7 +723,7 @@ void PlaceMarker(struct site location) /* Is Marker Position Clear Of Text Or Other Markers? */ for (x=lat-p3; (x<=xmax && x>=xmin && x<=lat+p3); x+=p1) - for (y=lon-p3; (y<=ymax && y>=ymin && y<=lon+p3); y+=p1) + for (y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1) occupied|=(GetMask(x,y)&2); if (occupied==0) @@ -573,7 +735,7 @@ void PlaceMarker(struct site location) label_length=p1*(double)(strlen(location.name)<<3); - if (((lon+label_length)<=ymax) && (lon-label_length)>=ymin) + if ((LonDiff(lon+label_length,ymax)<=0.0) && (LonDiff(lon-label_length,ymin)>=0.0)) { /* Default: Centered Text */ @@ -638,7 +800,7 @@ void PlaceMarker(struct site location) if (ok2print==0) { - if ((lon-label_length)>=ymin) + if (LonDiff(lon-label_length,ymin)>=0.0) { /* Position Text To The Right Of The Marker */ @@ -703,7 +865,7 @@ void PlaceMarker(struct site location) coordinates that describe the placement of the text on the map. */ - if (ok2print && textx!=0.0 && texty!=0.0) + if (ok2print) { /* Draw Text */ @@ -720,6 +882,7 @@ void PlaceMarker(struct site location) if (byte&c) OrMask(x,y,2); } + x-=p1; y=texty; } @@ -728,7 +891,7 @@ void PlaceMarker(struct site location) On Location Specified */ for (x=lat-p3; (x<=xmax && x>=xmin && x<=lat+p3); x+=p1) - for (y=lon-p3; (y<=ymax && y>=ymin && y<=lon+p3); y+=p1) + for (y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1) OrMask(x,y,2); } } @@ -746,10 +909,9 @@ double ReadBearing(char *input) embedded within the numbers expressed in the input string. Decimal seconds are permitted. */ - double bearing=0.0; - char string[20]; - int a, b, length, degrees, minutes; - double seconds; + double seconds, bearing=0.0; + char string[20]; + int a, b, length, degrees, minutes; /* Copy "input" to "string", and ignore any extra spaces that might be present in the process. */ @@ -759,7 +921,7 @@ double ReadBearing(char *input) for (a=0, b=0; a360.0 || bearing<0.0) + if (bearing>360.0 || bearing<-90.0) bearing=0.0; return bearing; @@ -803,10 +969,10 @@ struct site LoadQTH(char *filename) or 'm', or by the word "meters" or "Meters", in which case meters is assumed, and is handled accordingly. */ - int x; - char string[50], qthfile[255]; - struct site tempsite; - FILE *fd=NULL; + int x; + char string[50], qthfile[255]; + struct site tempsite; + FILE *fd=NULL; for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++) qthfile[x]=filename[x]; @@ -817,8 +983,8 @@ struct site LoadQTH(char *filename) qthfile[x+3]='h'; qthfile[x+4]=0; - tempsite.lat=0.0; - tempsite.lon=0.0; + tempsite.lat=91.0; + tempsite.lon=361.0; tempsite.alt=0.0; tempsite.name[0]=0; @@ -862,219 +1028,608 @@ struct site LoadQTH(char *filename) if (string[x]=='M' || string[x]=='m') { string[x]=0; - sscanf(string,"%lf",&tempsite.alt); + sscanf(string,"%f",&tempsite.alt); tempsite.alt*=3.28084; } else { string[x]=0; - sscanf(string,"%lf",&tempsite.alt); + sscanf(string,"%f",&tempsite.alt); } } return tempsite; } -int LoadSDF_SDF(char *name) +void LoadPAT(char *filename) { - /* This function reads uncompressed SPLAT Data Files (.sdf) - containing digital elevation model data into memory. - Elevation data, maximum and minimum elevations, and - quadrangle limits are stored in the first available - dem[] structure. */ - - int x, y, data, indx, minlat, minlon, maxlat, maxlon; - char found, free_slot=0, line[20], sdf_file[255], path_plus_name[255]; - FILE *fd; + /* This function reads and processes antenna pattern (.az + and .el) files that correspond in name to previously + loaded SPLAT! .lrp files. */ + + int a, b, w, x, y, z, last_index, next_index, span; + char string[255], azfile[255], elfile[255], *pointer=NULL; + float az, xx, elevation, amplitude, rotation, valid1, valid2, + delta, azimuth[361], azimuth_pattern[361], el_pattern[10001], + elevation_pattern[361][1001], slant_angle[361], tilt, + mechanical_tilt, tilt_azimuth, tilt_increment, sum; + FILE *fd=NULL; + unsigned char read_count[10001]; - for (x=0; name[x]!='.' && name[x]!=0 && x<250; x++) - sdf_file[x]=name[x]; + for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++) + { + azfile[x]=filename[x]; + elfile[x]=filename[x]; + } - sdf_file[x]=0; + azfile[x]='.'; + azfile[x+1]='a'; + azfile[x+2]='z'; + azfile[x+3]=0; - /* Parse filename for minimum latitude and longitude values */ + elfile[x]='.'; + elfile[x+1]='e'; + elfile[x+2]='l'; + elfile[x+3]=0; - sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon); + rotation=0.0; - sdf_file[x]='.'; - sdf_file[x+1]='s'; - sdf_file[x+2]='d'; - sdf_file[x+3]='f'; - sdf_file[x+4]=0; + /* Load .az antenna pattern file */ - /* Is it already in memory? */ + fd=fopen(azfile,"r"); - for (indx=0, found=0; indx=0 && indx=0 && x<=360 && fd!=NULL) + { + azimuth[x]+=amplitude; + read_count[x]++; + } - fgets(line,19,fd); - sscanf(line,"%d",&dem[indx].min_north); + fgets(string,254,fd); + pointer=strchr(string,';'); - fgets(line,19,fd); - sscanf(line,"%d",&dem[indx].min_west); + if (pointer!=NULL) + *pointer=0; - fgets(line,19,fd); - sscanf(line,"%d",&dem[indx].max_north); + sscanf(string,"%f %f",&az, &litude); - for (x=0; x<1200; x++) - for (y=0; y<1200; y++) - { - fgets(line,19,fd); - sscanf(line,"%d",&data); - dem[indx].data[x][y]=data; + } while (feof(fd)==0); - if (data>dem[indx].max_el) - dem[indx].max_el=data; + fclose(fd); - if (dem[indx].min_el==0) - dem[indx].min_el=data; - else - { - if (datamax_elevation) - max_elevation=dem[indx].max_el; + /* Average pattern values in case more than + one was read for each degree of azimuth. */ - if (dem[indx].max_north>max_north) - max_north=dem[indx].max_north; + for (x=0; x<=360; x++) + { + if (read_count[x]>1) + azimuth[x]/=(float)read_count[x]; + } - if (dem[indx].max_west>max_west) - max_west=dem[indx].max_west; + /* Interpolate missing azimuths + to completely fill the array */ - if (min_north==0) - min_north=dem[indx].min_north; - else + last_index=-1; + next_index=-1; + + for (x=0; x<=360; x++) + { + if (read_count[x]!=0) { - if (dem[indx].min_north=360) + y-=360; - static int x, y, nBuf; - static char buffer[BZBUFFER+1], output[BZBUFFER+1]; - char done=0; + azimuth_pattern[y]=azimuth[x]; + } - if (opened!=1 && bzerror==BZ_OK) - { - /* First time through. Initialize everything! */ + azimuth_pattern[360]=azimuth_pattern[0]; - x=0; - y=0; - nBuf=0; - opened=1; - output[0]=0; + got_azimuth_pattern=255; } - do + /* Read and process .el file */ + + fd=fopen(elfile,"r"); + + if (fd!=NULL) { - if (x==nBuf && bzerror!=BZ_STREAM_END && bzerror==BZ_OK && opened) + for (x=0; x<=10000; x++) { - /* Uncompress data into a static buffer */ - - nBuf=BZ2_bzRead(&bzerror, bzfd, buffer, BZBUFFER); - buffer[nBuf]=0; - x=0; + el_pattern[x]=0.0; + read_count[x]=0; } - /* Build a string from buffer contents */ + /* Read mechanical tilt (degrees) and + tilt azimuth in degrees measured + clockwise from true North. */ - output[y]=buffer[x]; + fgets(string,254,fd); + pointer=strchr(string,';'); - if (output[y]=='\n' || output[y]==0 || y==(int)length-1) - { - output[y+1]=0; - done=1; - y=0; - } + if (pointer!=NULL) + *pointer=0; - else - y++; - x++; + sscanf(string,"%f %f",&mechanical_tilt, &tilt_azimuth); - } while (done==0); + /* Read elevation (degrees) and corresponding + normalized field radiation pattern amplitude + (0.0 to 1.0) until EOF is reached. */ - if (output[0]==0) - opened=0; + fgets(string,254,fd); + pointer=strchr(string,';'); - return (output); -} + if (pointer!=NULL) + *pointer=0; + + sscanf(string,"%f %f", &elevation, &litude); + + while (feof(fd)==0) + { + /* Read in normalized radiated field values + for every 0.01 degrees of elevation between + -10.0 and +90.0 degrees */ + + x=(int)rintf(100.0*(elevation+10.0)); + + if (x>=0 && x<=10000) + { + el_pattern[x]+=amplitude; + read_count[x]++; + } + + fgets(string,254,fd); + pointer=strchr(string,';'); + + if (pointer!=NULL) + *pointer=0; + + sscanf(string,"%f %f", &elevation, &litude); + } + + fclose(fd); + + /* Average the field values in case more than + one was read for each 0.01 degrees of elevation. */ + + for (x=0; x<=10000; x++) + { + if (read_count[x]>1) + el_pattern[x]/=(float)read_count[x]; + } + + /* Interpolate between missing elevations (if + any) to completely fill the array and provide + radiated field values for every 0.01 degrees of + elevation. */ + + last_index=-1; + next_index=-1; + + for (x=0; x<=10000; x++) + { + if (read_count[x]!=0) + { + if (last_index==-1) + last_index=x; + else + next_index=x; + } + + if (last_index!=-1 && next_index!=-1) + { + valid1=el_pattern[last_index]; + valid2=el_pattern[next_index]; + + span=next_index-last_index; + delta=(valid2-valid1)/(float)span; + + for (y=last_index+1; y=360) + y-=360; + + while (y<0) + y+=360; + + if (x<=180) + slant_angle[y]=-(tilt_increment*(90.0-xx)); + + if (x>180) + slant_angle[y]=-(tilt_increment*(xx-270.0)); + } + } + + slant_angle[360]=slant_angle[0]; /* 360 degree wrap-around */ + + for (w=0; w<=360; w++) + { + tilt=slant_angle[w]; + + /** Convert tilt angle to + an array index offset **/ + + y=(int)rintf(100.0*tilt); + + /* Copy shifted el_pattern[10001] field + values into elevation_pattern[361][1001] + at the corresponding azimuth, downsampling + (averaging) along the way in chunks of 10. */ + + for (x=y, z=0; z<=1000; x+=10, z++) + { + for (sum=0.0, a=0; a<10; a++) + { + b=a+x; + + if (b>=0 && b<=10000) + sum+=el_pattern[b]; + if (b<0) + sum+=el_pattern[0]; + if (b>10000) + sum+=el_pattern[10000]; + } + + elevation_pattern[w][z]=sum/10.0; + } + } + + got_elevation_pattern=255; + } + + for (x=0; x<=360; x++) + { + for (y=0; y<=1000; y++) + { + if (got_elevation_pattern) + elevation=elevation_pattern[x][y]; + else + elevation=1.0; + + if (got_azimuth_pattern) + az=azimuth_pattern[x]; + else + az=1.0; + + LR.antenna_pattern[x][y]=az*elevation; + } + } +} + +int LoadSDF_SDF(char *name) +{ + /* This function reads uncompressed SPLAT Data Files (.sdf) + containing digital elevation model data into memory. + Elevation data, maximum and minimum elevations, and + quadrangle limits are stored in the first available + dem[] structure. */ + + int x, y, data, indx, minlat, minlon, maxlat, maxlon; + char found, free_slot=0, line[20], sdf_file[255], + path_plus_name[255]; + FILE *fd; + + for (x=0; name[x]!='.' && name[x]!=0 && x<250; x++) + sdf_file[x]=name[x]; + + sdf_file[x]=0; + + /* Parse filename for minimum latitude and longitude values */ + + sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon); + + sdf_file[x]='.'; + sdf_file[x+1]='s'; + sdf_file[x+2]='d'; + sdf_file[x+3]='f'; + sdf_file[x+4]=0; + + /* Is it already in memory? */ + + for (indx=0, found=0; indx=0 && indxdem[indx].max_el) + dem[indx].max_el=data; + + if (datamax_elevation) + max_elevation=dem[indx].max_el; + + if (max_north==-90) + max_north=dem[indx].max_north; + + else if (dem[indx].max_north>max_north) + max_north=dem[indx].max_north; + + if (min_north==90) + min_north=dem[indx].min_north; + + else if (dem[indx].min_northmax_west) + max_west=dem[indx].max_west; + } + + else + { + if (dem[indx].max_westmin_west) + min_west=dem[indx].min_west; + } + } + + fprintf(stdout," Done!\n"); + fflush(stdout); + return 1; + } + + else + return -1; + } + + else + return 0; +} + +char *BZfgets(BZFILE *bzfd, unsigned length) +{ + /* This function returns at most one less than 'length' number + of characters from a bz2 compressed file whose file descriptor + is pointed to by *bzfd. In operation, a buffer is filled with + uncompressed data (size = BZBUFFER), which is then parsed + and doled out as NULL terminated character strings every time + this function is invoked. A NULL string indicates an EOF + or error condition. */ + + static int x, y, nBuf; + static char buffer[BZBUFFER+1], output[BZBUFFER+1]; + char done=0; + + if (opened!=1 && bzerror==BZ_OK) + { + /* First time through. Initialize everything! */ + + x=0; + y=0; + nBuf=0; + opened=1; + output[0]=0; + } + + do + { + if (x==nBuf && bzerror!=BZ_STREAM_END && bzerror==BZ_OK && opened) + { + /* Uncompress data into a static buffer */ + + nBuf=BZ2_bzRead(&bzerror, bzfd, buffer, BZBUFFER); + buffer[nBuf]=0; + x=0; + } + + /* Build a string from buffer contents */ + + output[y]=buffer[x]; + + if (output[y]=='\n' || output[y]==0 || y==(int)length-1) + { + output[y+1]=0; + done=1; + y=0; + } + + else + y++; + x++; + + } while (done==0); + + if (output[0]==0) + opened=0; + + return (output); +} int LoadSDF_BZ(char *name) { @@ -1083,10 +1638,10 @@ int LoadSDF_BZ(char *name) maximum and minimum elevations, and quadrangle limits are stored in the first available dem[] structure. */ - int x, y, data, indx, minlat, minlon, maxlat, maxlon; - char found, free_slot=0, sdf_file[255], path_plus_name[255]; - FILE *fd; - BZFILE *bzfd; + int x, y, data, indx, minlat, minlon, maxlat, maxlon; + char found, free_slot=0, sdf_file[255], path_plus_name[255]; + FILE *fd; + BZFILE *bzfd; for (x=0; name[x]!='.' && name[x]!=0 && x<247; x++) sdf_file[x]=name[x]; @@ -1111,11 +1666,8 @@ int LoadSDF_BZ(char *name) for (indx=0, found=0; indx=0 && indx=0 && indxdem[indx].max_el) dem[indx].max_el=data; - if (dem[indx].min_el==0) + if (datamax_elevation) max_elevation=dem[indx].max_el; - if (dem[indx].max_north>max_north) + if (max_north==-90) max_north=dem[indx].max_north; - if (dem[indx].max_west>max_west) - max_west=dem[indx].max_west; + else if (dem[indx].max_north>max_north) + max_north=dem[indx].max_north; + + if (min_north==90) + min_north=dem[indx].min_north; - if (min_north==0) + else if (dem[indx].min_northmax_west) + max_west=dem[indx].max_west; + } + + else + { + if (dem[indx].max_westmin_west) + min_west=dem[indx].min_west; + } } fprintf(stdout," Done!\n"); fflush(stdout); return 1; } + else return -1; } + else return 0; } @@ -1225,9 +1808,9 @@ char LoadSDF(char *name) exists for the region requested, and that the region requested must be entirely over water. */ - int x, y, indx, minlat, minlon, maxlat, maxlon; - char found, free_slot=0; - int return_value=-1; + int x, y, indx, minlat, minlon, maxlat, maxlon; + char found, free_slot=0; + int return_value=-1; /* Try to load an uncompressed SDF first. */ @@ -1250,11 +1833,8 @@ char LoadSDF(char *name) for (indx=0, found=0; indx=0 && indx=0 && indxmax_elevation) max_elevation=dem[indx].max_el; - if (dem[indx].max_north>max_north) + if (max_north==-90) max_north=dem[indx].max_north; - if (dem[indx].max_west>max_west) - max_west=dem[indx].max_west; + else if (dem[indx].max_north>max_north) + max_north=dem[indx].max_north; + + if (min_north==90) + min_north=dem[indx].min_north; - if (min_north==0) + else if (dem[indx].min_northmax_west) + max_west=dem[indx].max_west; + } + + else + { + if (dem[indx].max_westmin_west) + min_west=dem[indx].min_west; + } } fprintf(stdout," Done!\n"); @@ -1331,10 +1939,10 @@ void LoadCities(char *filename) the locations and names of the cities and site locations read on topographic maps generated by SPLAT! */ - int x, y, z; - char input[80], str[3][80]; - struct site city_site; - FILE *fd=NULL; + int x, y, z; + char input[80], str[3][80]; + struct site city_site; + FILE *fd=NULL; fd=fopen(filename,"r"); @@ -1370,15 +1978,155 @@ void LoadCities(char *filename) city_site.lon=ReadBearing(str[2]); city_site.alt=0.0; - PlaceMarker(city_site); + PlaceMarker(city_site); + + fgets(input,78,fd); + } + + fclose(fd); + fprintf(stdout,"Done!\n"); + fflush(stdout); + } + + else + fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename); +} + +void LoadUDT(char *filename) +{ + /* This function reads a file containing User-Defined Terrain + features for their addition to the digital elevation model + data used by SPLAT!. Elevations in the UDT file are evaluated + and then copied into a temporary file under /tmp. Then the + contents of the temp file are scanned, and if found to be unique, + are added to the ground elevations described by the digital + elevation data already loaded into memory. */ + + int i, x, y, z, fd=0; + char input[80], str[3][80], tempname[15], *pointer=NULL; + double latitude, longitude, height, templat, templon, + tempheight, one_pixel; + FILE *fd1=NULL, *fd2=NULL; + + strcpy(tempname,"/tmp/XXXXXX\0"); + one_pixel=1.0/1200.0; + + fd1=fopen(filename,"r"); + + if (fd1!=NULL) + { + fd=mkstemp(tempname); + fd2=fopen(tempname,"w"); + + fgets(input,78,fd1); + + pointer=strchr(input,';'); + + if (pointer!=NULL) + *pointer=0; + + fprintf(stdout,"Reading \"%s\"... ",filename); + fflush(stdout); + + while (feof(fd1)==0) + { + /* Parse line for latitude, longitude, height */ + + for (x=0, y=0, z=0; x<78 && input[x]!=0 && z<3; x++) + { + if (input[x]!=',' && y<78) + { + str[z][y]=input[x]; + y++; + } + + else + { + str[z][y]=0; + z++; + y=0; + } + } + + latitude=ReadBearing(str[0]); + longitude=ReadBearing(str[1]); + + /* Remove and/or from antenna height string */ + + for (i=0; str[2][i]!=13 && str[2][i]!=10 && str[2][i]!=0; i++); + + str[2][i]=0; + + /* The terrain feature may be expressed in either + feet or meters. If the letter 'M' or 'm' is + discovered in the string, then this is an + indication that the value given is expressed + in meters. Otherwise the height is interpreted + as being expressed in feet. */ + + for (i=0; str[2][i]!='M' && str[2][i]!='m' && str[2][i]!=0 && i<48; i++); + + if (str[2][i]=='M' || str[2][i]=='m') + { + str[2][i]=0; + height=rint(atof(str[2])); + } + + else + { + str[2][i]=0; + height=rint(3.28084*atof(str[2])); + } + + if (height>0.0) + fprintf(fd2,"%f, %f, %f\n",latitude, longitude, height); + + fgets(input,78,fd1); + + pointer=strchr(input,';'); + + if (pointer!=NULL) + *pointer=0; + } + + fclose(fd1); + fclose(fd2); + close(fd); + + fprintf(stdout,"Done!\n"); + fflush(stdout); + + fd1=fopen(tempname,"r"); + fd2=fopen(tempname,"r"); + + fscanf(fd1,"%lf, %lf, %lf", &latitude, &longitude, &height); + + for (y=0; feof(fd1)==0; y++) + { + rewind(fd2); + + fscanf(fd2,"%lf, %lf, %lf", &templat, &templon, &tempheight); + + for (x=0, z=0; feof(fd2)==0; x++) + { + if (x>y) + if (fabs(latitude-templat)<=one_pixel && fabs(longitude-templon)<=one_pixel) + z=1; + + fscanf(fd2,"%lf, %lf, %lf", &templat, &templon, &tempheight); + } + + if (z==0) + AddElevation(latitude, longitude, height); - fgets(input,78,fd); + fscanf(fd1,"%lf, %lf, %lf", &latitude, &longitude, &height); } - fclose(fd); - fprintf(stdout,"Done!\n"); - fflush(stdout); + fclose(fd1); + fclose(fd2); + unlink(tempname); } + else fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename); } @@ -1391,11 +2139,11 @@ void LoadBoundaries(char *filename) the coordinates that describe the boundaries of cities, counties, and states. */ - int x; - double lat0, lon0, lat1, lon1; - char string[80]; - struct site source, destination; - FILE *fd=NULL; + int x; + double lat0, lon0, lat1, lon1; + char string[80]; + struct site source, destination; + FILE *fd=NULL; fd=fopen(filename,"r"); @@ -1415,7 +2163,7 @@ void LoadBoundaries(char *filename) do { sscanf(string,"%lf %lf", &lon1, &lat1); - + lon0=fabs(lon0); lon1=fabs(lon1); @@ -1445,6 +2193,7 @@ void LoadBoundaries(char *filename) fprintf(stdout,"Done!\n"); fflush(stdout); } + else fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename); } @@ -1457,12 +2206,12 @@ void ReadLRParm(char *txsite_filename) is not found, then the file "splat.lrp" is read from the current working directory. Failure to load this file will result in the default parameters hard coded into this - being used and written to "splat.lrp". */ + function to be used and written to "splat.lrp". */ - double din; - char filename[255], lookup[256], string[80]; - int iin, ok=0, x; - FILE *fd=NULL, *outfile=NULL; + double din; + char filename[255], string[80], *pointer=NULL; + int iin, ok=0, x; + FILE *fd=NULL, *outfile=NULL; /* Default parameters in case things go bad */ @@ -1487,20 +2236,6 @@ void ReadLRParm(char *txsite_filename) filename[x+3]='p'; filename[x+4]=0; - /* Small lookup table to parse file, pass - numeric data, and ignore comments. */ - - for (x=0; x<=255; x++) - lookup[x]=0; - - /* Valid characters */ - - for (x=48; x<=57; x++) - lookup[x]=x; - - lookup[32]=32; - lookup[46]='.'; - fd=fopen(filename,"r"); if (fd==NULL) @@ -1515,10 +2250,10 @@ void ReadLRParm(char *txsite_filename) { fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%lf", &din); @@ -1528,10 +2263,10 @@ void ReadLRParm(char *txsite_filename) fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%lf", &din); } @@ -1542,10 +2277,10 @@ void ReadLRParm(char *txsite_filename) fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%lf", &din); } @@ -1556,10 +2291,10 @@ void ReadLRParm(char *txsite_filename) fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%lf", &din); } @@ -1570,10 +2305,10 @@ void ReadLRParm(char *txsite_filename) fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%d", &iin); } @@ -1584,10 +2319,10 @@ void ReadLRParm(char *txsite_filename) fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%d", &iin); } @@ -1598,10 +2333,10 @@ void ReadLRParm(char *txsite_filename) fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%lf", &din); } @@ -1612,18 +2347,21 @@ void ReadLRParm(char *txsite_filename) fgets(string,80,fd); - for (x=0; lookup[(int)string[x]] && x<20; x++) - string[x]=lookup[(int)string[x]]; + pointer=strchr(string,';'); - string[x]=0; + if (pointer!=NULL) + *pointer=0; ok=sscanf(string,"%lf", &din); } + fclose(fd); + if (ok) + { LR.rel=din; - - fclose(fd); + LoadPAT(filename); + } } if (fd==NULL) @@ -1634,21 +2372,13 @@ void ReadLRParm(char *txsite_filename) outfile=fopen("splat.lrp","w"); fprintf(outfile,"%.3f\t; Earth Dielectric Constant (Relative permittivity)\n",LR.eps_dielect); - fprintf(outfile,"%.3f\t; Earth Conductivity (Siemens per meter)\n", LR.sgm_conductivity); - fprintf(outfile,"%.3f\t; Atmospheric Bending Constant (N-Units)\n",LR.eno_ns_surfref); - fprintf(outfile,"%.3f\t; Frequency in MHz (20 MHz to 20 GHz)\n", LR.frq_mhz); - fprintf(outfile,"%d\t; Radio Climate\n",LR.radio_climate); - fprintf(outfile,"%d\t; Polarization (0 = Horizontal, 1 = Vertical)\n", LR.pol); - fprintf(outfile,"%.2f\t; Fraction of situations\n",LR.conf); - fprintf(outfile, "%.2f\t; Fraction of time\n",LR.rel); - fprintf(outfile,"\nPlease consult SPLAT! documentation for the meaning and use of this data.\n"); fclose(outfile); @@ -1660,98 +2390,6 @@ void ReadLRParm(char *txsite_filename) fprintf(stderr,"Longley-Rice default parameters have been assumed for this analysis.\n"); } -struct site los(struct site source, struct site destination) -{ - /* This function determines whether a line-of-sight path - unobstructed by terrain exists between source (transmitter) - and destination (receiver) based on the geographical - locations of the two sites, their respective antenna - heights above ground, and the terrain between them. - A site structure is returned upon completion. If the - first character of site.name is ' ', then a clear path - exists between source and destination. If the first - character is '*', then an obstruction exists, and the - site.lat and site.lon elements of the structure provide - the geographical location of the obstruction. */ - - int x; - char block; - struct site test, blockage; - register double distance, tx_alt, rx_alt, - cos_xmtr_angle, cos_test_angle, test_alt; - - ReadPath(source,destination); - - distance=5280.0*Distance(source,destination); - tx_alt=earthradius+source.alt+GetElevation(source); - rx_alt=earthradius+destination.alt+GetElevation(destination); - - /* Elevation angle of the xmtr (source) as seen by the rcvr */ - - cos_xmtr_angle=((rx_alt*rx_alt)+(distance*distance)-(tx_alt*tx_alt))/(2.0*rx_alt*distance); - - /* Determine the elevation angle of each discrete location - along the path between the receiver and transmitter. - - Since obstructions are more likely due to terrain effects - closest to the receiver rather than farther away, we start - looking for potential obstructions from the receiver's - location, and work our way towards the transmitter. - This loop is broken when the first obstruction is - detected. If we can travel all the way to the transmitter - without detecting an obstruction, then we have a clear - unobstructed path between transmitter and receiver. */ - - for (x=path.length-1, block=0; x>0 && block==0; x--) - { - /* Build a structure for each test - point along the path to be surveyed. */ - - test.lat=path.lat[x]; - test.lon=path.lon[x]; - - /* Measure the distance between the - test point and the receiver locations */ - - distance=5280.0*Distance(test,destination); - test_alt=earthradius+path.elevation[x]; - - /* Determine the cosine of the elevation of the test - point as seen from the location of the receiver */ - - cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance); - - /* If the elevation angle to the test point (as seen from - the receiver) is greater than the elevation angle to the - transmitter (as seen by the receiver), then we have a - path obstructed by terrain. Note: Since we're comparing - the cosines of these angles rather than the angles - themselves (eliminating the call to acos() saves - considerable time), the following "if" statement is - reversed from what it would normally be if the angles - were compared. */ - - if (cos_xmtr_angle>cos_test_angle) - { - block=1; - blockage.lat=path.lat[x]; - blockage.lon=path.lon[x]; - blockage.alt=path.elevation[x]; - blockage.name[0]='*'; - } - } - - if (block==0) - { - blockage.lat=0.0; - blockage.lon=0.0; - blockage.alt=0.0; - blockage.name[0]=' '; - } - - return blockage; -} - void PlotPath(struct site source, struct site destination, char mask_value) { /* This function analyzes the path between the source and @@ -1794,7 +2432,7 @@ void PlotPath(struct site source, struct site destination, char mask_value) cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance); /* Compare these two angles to determine if - a blockage exists. Since we're comparing + an obstruction exists. Since we're comparing the cosines of these angles rather than the angles themselves, the following "if" statement is reversed from what it would @@ -1810,39 +2448,145 @@ void PlotPath(struct site source, struct site destination, char mask_value) } } -void PlotLRPath(struct site source, struct site destination) +void PlotLRPath(struct site source, struct site destination, FILE *fd) { - /* This function plots the RF signal path loss - between source and destination points based - on the Longley-Rice propagation model. */ - - char strmode[100]; - int x, y, errnum; - double loss; + /* This function plots the RF path loss between source and + destination points based on the Longley-Rice propagation + model, taking into account antenna pattern data, if available. */ + + char block=0, strmode[100]; + int x, y, errnum; + double loss, azimuth, pattern=0.0, + source_alt, dest_alt, source_alt2, dest_alt2, + cos_xmtr_angle, cos_test_angle=0.0, test_alt, + elevation, distance=0.0, four_thirds_earth; + struct site temp; ReadPath(source,destination); - elev_l[1]=0.04*METERS_PER_MILE; + + four_thirds_earth=EARTHRADIUS*(4.0/3.0); + + /* Copy elevations along path into the elev_l[] array. */ for (x=0; xcos_test_angle) + block=1; + } + + /* At this point, we have the elevation angle + to the first obstruction (if it exists). */ + } + + /* Determine attenuation for each point along the + path using Longley-Rice's point_to_point mode + starting at y=2 (number_of_points = 1), the + shortest distance terrain can play a role in + path loss. */ + + elev_l[0]=y-1; /* (number of points - 1) */ + + /* Distance between elevation samples */ + elev_l[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]); point_to_point(elev_l,source.alt*METERS_PER_FOOT, - destination.alt*METERS_PER_FOOT, - LR.eps_dielect, LR.sgm_conductivity, LR.eno_ns_surfref, - LR.frq_mhz, LR.radio_climate, LR.pol, LR.conf, LR.rel, - loss, strmode, errnum); + destination.alt*METERS_PER_FOOT, LR.eps_dielect, + LR.sgm_conductivity, LR.eno_ns_surfref, LR.frq_mhz, + LR.radio_climate, LR.pol, LR.conf, LR.rel, loss, + strmode, errnum); + + if (block) + elevation=((acos(cos_test_angle))/deg2rad)-90.0; + + else + elevation=((acos(cos_xmtr_angle))/deg2rad)-90.0; + + temp.lat=path.lat[y]; + temp.lon=path.lon[y]; - /* Note: PASS BY REFERENCE ... loss and errnum are pass - by reference, only used in this file by this function */ + azimuth=(Azimuth(source,temp)); + + if (fd!=NULL) + { + /* Write path loss data to output file */ + + fprintf(fd,"%.7f, %.7f, %.3f, %.3f, %.2f\n",path.lat[y], path.lon[y], azimuth, elevation, loss); + } + + /* Integrate the antenna's radiation + pattern into the overall path loss. */ + + x=(int)rint(10.0*(10.0-elevation)); + + if (x>=0 && x<=1000) + { + azimuth=rint(azimuth); + + pattern=(double)LR.antenna_pattern[(int)azimuth][x]; + + if (pattern!=0.0) + { + pattern=20.0*log10(pattern); + loss-=pattern; + } + } if (loss>225.0) loss=225.0; @@ -1857,7 +2601,7 @@ void PlotLRPath(struct site source, struct site destination) OrMask(path.lat[y],path.lon[y],((unsigned char)(loss))<<3); } - else if (GetMask(path.lat[y],path.lon[y])==0 && 0.04*y>max_range) + else if (GetMask(path.lat[y],path.lon[y])==0 && path.distance[y]>max_range) OrMask(path.lat[y],path.lon[y],1); } } @@ -1873,7 +2617,7 @@ void PlotCoverage(struct site source, double altitude) of a topographic map when the WritePPM() function is later invoked. */ - double lat, lon, one_pixel; + float lat, lon, one_pixel; static unsigned char mask_value; int z, count; struct site edge; @@ -1893,16 +2637,19 @@ void PlotCoverage(struct site source, double altitude) count=0; - fprintf(stdout,"\nComputing line-of-sight coverage of %s with an RX antenna\nat %.2f feet AGL:\n\n 0%c to 25%c ",source.name,altitude,37,37); + fprintf(stdout,"\nComputing line-of-sight coverage of %s with an RX antenna\nat %.2f %s AGL:\n\n 0%c to 25%c ",source.name,metric?altitude*METERS_PER_FOOT:altitude,metric?"meters":"feet",37,37); fflush(stdout); /* 18.75=1200 pixels/degree divided by 64 loops per progress indicator symbol (.oOo) printed. */ - z=(int)(18.75*(max_west-min_west)); + z=(int)(18.75*ReduceAngle(max_west-min_west)); - for (lon=min_west, x=0; lon<=max_west; lon+=one_pixel) + for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) { + if (lon>=360.0) + lon-=360.0; + edge.lat=max_north; edge.lon=lon; edge.alt=altitude; @@ -1955,10 +2702,13 @@ void PlotCoverage(struct site source, double altitude) fprintf(stdout,"\n50%c to 75%c ",37,37); fflush(stdout); - z=(int)(18.75*(max_west-min_west)); + z=(int)(18.75*ReduceAngle(max_west-min_west)); - for (lon=min_west, x=0; lon<=max_west; lon+=one_pixel) + for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) { + if (lon>=360.0) + lon-=360.0; + edge.lat=min_north; edge.lon=lon; edge.alt=altitude; @@ -2027,7 +2777,7 @@ void PlotCoverage(struct site source, double altitude) } } -void PlotLRMap(struct site source, double altitude) +void PlotLRMap(struct site source, double altitude, char *plo_filename) { /* This function performs a 360 degree sweep around the transmitter site (source location), and plots the @@ -2040,8 +2790,9 @@ void PlotLRMap(struct site source, double altitude) int z, count; struct site edge; - double lat, lon, one_pixel; + float lat, lon, one_pixel; unsigned char symbol[4], x; + FILE *fd=NULL; one_pixel=1.0/1200.0; @@ -2053,21 +2804,35 @@ void PlotLRMap(struct site source, double altitude) count=0; fprintf(stdout,"\nComputing Longley-Rice coverage of %s ", source.name); - fprintf(stdout,"out to a radius\nof %.2f miles with an RX antenna at %.2f feet AGL:\n\n 0%c to 25%c ",max_range,altitude,37,37); + + fprintf(stdout,"out to a radius\nof %.2f %s with an RX antenna at %.2f %s AGL:\n\n 0%c to 25%c ",metric?max_range*KM_PER_MILE:max_range,metric?"kilometers":"miles",metric?altitude*METERS_PER_FOOT:altitude,metric?"meters":"feet",37,37); fflush(stdout); + if (plo_filename[0]!=0) + fd=fopen(plo_filename,"wb"); + + if (fd!=NULL) + { + /* Write header information to output file */ + + 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); + } + /* 18.75=1200 pixels/degree divided by 64 loops per progress indicator symbol (.oOo) printed. */ - z=(int)(18.75*(max_west-min_west)); + z=(int)(18.75*ReduceAngle(max_west-min_west)); - for (lon=min_west, x=0; lon<=max_west; lon+=one_pixel) + for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) { + if (lon>=360.0) + lon-=360.0; + edge.lat=max_north; edge.lon=lon; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,fd); count++; if (count==z) @@ -2095,7 +2860,7 @@ void PlotLRMap(struct site source, double altitude) edge.lon=min_west; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,fd); count++; if (count==z) @@ -2115,15 +2880,18 @@ void PlotLRMap(struct site source, double altitude) fprintf(stdout,"\n50%c to 75%c ",37,37); fflush(stdout); - z=(int)(18.75*(max_west-min_west)); + z=(int)(18.75*ReduceAngle(max_west-min_west)); - for (lon=min_west, x=0; lon<=max_west; lon+=one_pixel) + for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) { + if (lon>=360.0) + lon-=360.0; + edge.lat=min_north; edge.lon=lon; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,fd); count++; if (count==z) @@ -2151,7 +2919,7 @@ void PlotLRMap(struct site source, double altitude) edge.lon=max_west; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,fd); count++; if (count==z) @@ -2167,11 +2935,14 @@ void PlotLRMap(struct site source, double altitude) } } + if (fd!=NULL) + fclose(fd); + fprintf(stdout,"\nDone!\n"); fflush(stdout); } -void WritePPM(char *filename) +void WritePPM(char *filename, unsigned char geo) { /* This function generates a topographic map in Portable Pix Map (PPM) format based on logarithmically scaled topology data, @@ -2180,32 +2951,54 @@ void WritePPM(char *filename) from its representation in dem[][] so that north points up and east points right in the image generated. */ - int indx, x, x0, y0, minlat, minlon; - unsigned width, height, output; + char mapfile[255], geofile[255]; unsigned char found, mask; - char mapfile[255]; - double conversion, lat, lon, one_over_gamma, one_pixel; + unsigned width, height, output; + int indx, x, y, x0=0, y0=0, minlat, minlon; + float lat, lon, one_pixel, conversion, one_over_gamma; FILE *fd; one_pixel=1.0/1200.0; one_over_gamma=1.0/GAMMA; conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma); - width=1200*(max_west-min_west); - height=1200*(max_north-min_north); + width=(unsigned)(1200*ReduceAngle(max_west-min_west)); + height=(unsigned)(1200*ReduceAngle(max_north-min_north)); if (filename[0]==0) strncpy(mapfile, "map.ppm\0",8); else { for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++) + { mapfile[x]=filename[x]; + geofile[x]=filename[x]; + } mapfile[x]='.'; + geofile[x]='.'; mapfile[x+1]='p'; + geofile[x+1]='g'; mapfile[x+2]='p'; + geofile[x+2]='e'; mapfile[x+3]='m'; + geofile[x+3]='o'; mapfile[x+4]=0; + geofile[x+4]=0; + } + + if (geo) + { + 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%d.000\t\t%d.000\n",(max_west<180?-max_west:360-max_west),max_north); + fprintf(fd,"TIEPOINT\t%u\t%u\t%d.000\t\t%d.000\n",width-1,height-1,(min_west<180?-min_west:360-min_west),min_north); + fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height); + fprintf(fd,"#\n# Auto Generated by SPLAT! v%s\n#\n",splat_version); + + fclose(fd); } fd=fopen(mapfile,"wb"); @@ -2215,11 +3008,15 @@ void WritePPM(char *filename) fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height); fflush(stdout); - for (lat=(double)max_north; lat>=(double)min_north; lat-=one_pixel) + for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel) { - for (lon=(double)max_west; lon>=(double)min_west; lon-=one_pixel) + minlat=(int)floor(lat); + + for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel) { - minlat=(int)floor(lat); + if (lon<0.0) + lon+=360.0; + minlon=(int)floor(lon); for (indx=0, found=0; indx=(double)min_north; lat-=one_pixel) + for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel) { - for (lon=(double)max_west; lon>=(double)min_west; lon-=one_pixel) + minlat=(int)floor(lat); + + for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel) { - minlat=(int)floor(lat); + if (lon<0.0) + lon+=360.0; + minlon=(int)floor(lon); for (indx=0, found=0; indx>3)); + cityorcounty=0; if (mask&2) { /* Text Labels - Black or Red */ - if (mask&120) + + if ((mask&120) && (loss<=90)) fprintf(fd,"%c%c%c",0,0,0); 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); - else if (mask&1 && !((mask&248)==192)) - { - /* Outside Analysis Range */ - /* Display Greyscale / Sea Level */ + fprintf(fd,"%c%c%c",0,0,0); - if (dem[indx].data[x0][y0]==0) - fprintf(fd,"%c%c%c",0,0,170); - else - { - output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); - fprintf(fd,"%c%c%c",output,output,output); - } + cityorcounty=1; } - else switch ((mask&248)>>3) + if (cityorcounty==0) { - case 0: - /* Inside range, but no coverage. - Display Sea Level / Terrain */ + if (loss>maxdB) + + { /* Display land or sea elevation */ + + if (dem[indx].data[x0][y0]==0) + fprintf(fd,"%c%c%c",0,0,170); + else + { + /* Elevation: Greyscale */ + output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); + fprintf(fd,"%c%c%c",output,output,output); + } + } - if (dem[indx].data[x0][y0]==0) - fprintf(fd,"%c%c%c",0,0,170); - else + else switch (loss) { - /* Elevation: Greyscale */ - output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); - fprintf(fd,"%c%c%c",output,output,output); - } + /* Plot signal loss in color */ - break; + case 80: + fprintf(fd,"%c%c%c",255,0,0); + break; - case 1: - /* Green */ - fprintf(fd,"%c%c%c",0,255,0); - break; + case 90: + fprintf(fd,"%c%c%c",255,128,0); + break; - case 2: - /* Pink */ - fprintf(fd,"%c%c%c",255,192,203); - break; + case 100: + fprintf(fd,"%c%c%c",255,165,0); + break; - case 3: - /* Cyan */ - fprintf(fd,"%c%c%c",0,255,255); - break; + case 110: + fprintf(fd,"%c%c%c",255,206,0); + break; - case 4: - /* Yellow */ - fprintf(fd,"%c%c%c",255,255,0); - break; + case 120: + fprintf(fd,"%c%c%c",255,255,0); + break; - case 5: - /* Medium Violet */ - fprintf(fd,"%c%c%c",161,131,224); - break; + case 130: + fprintf(fd,"%c%c%c",184,255,0); + break; - case 6: - /* Orange */ - fprintf(fd,"%c%c%c",255,165,0); - break; + case 140: + fprintf(fd,"%c%c%c",0,255,0); + break; - case 7: - /* Light Green */ - fprintf(fd,"%c%c%c",193,255,193); - break; + case 150: + fprintf(fd,"%c%c%c",0,208,0); + break; - case 8: - /* Red Pink */ - fprintf(fd,"%c%c%c",255,108,108); - break; + case 160: + fprintf(fd,"%c%c%c",0,196,196); + break; - case 9: - /* TX1 + TX4: Green Yellow */ - fprintf(fd,"%c%c%c",173,255,47); - break; + case 170: + fprintf(fd,"%c%c%c",0,148,255); + break; - case 10: - /* Blanched Almond */ - fprintf(fd,"%c%c%c",255,235,184); - break; + case 180: + fprintf(fd,"%c%c%c",80,80,255); + break; - case 11: - /* Dark Turquoise */ - fprintf(fd,"%c%c%c",0,206,209); - break; + case 190: + fprintf(fd,"%c%c%c",0,38,255); + break; - case 12: - /* Tan */ - fprintf(fd,"%c%c%c",210,180,140); - break; + case 200: + fprintf(fd,"%c%c%c",142,63,255); + break; - case 13: - /* Magenta 1 */ - fprintf(fd,"%c%c%c",243,110,205); - break; + case 210: + fprintf(fd,"%c%c%c",196,54,255); + break; - case 14: - /* Gold2 */ - fprintf(fd,"%c%c%c",238,201,0); - break; + case 220: + fprintf(fd,"%c%c%c",255,0,255); + break; - case 15: - /* Medium Spring Green */ - fprintf(fd,"%c%c%c",0,250,154); - break; + case 230: + fprintf(fd,"%c%c%c",255,194,204); + break; - case 16: - /* Very light Blue */ - fprintf(fd,"%c%c%c",244,244,255); - break; + default: - default: - /* Land / Sea */ - if (dem[indx].data[x0][y0]==0) - fprintf(fd,"%c%c%c",0,0,170); - else - { - /* Elevation: Greyscale */ - output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); - fprintf(fd,"%c%c%c",output,output,output); + if (dem[indx].data[x0][y0]==0) + fprintf(fd,"%c%c%c",0,0,170); + else + { + /* Elevation: Greyscale */ + output=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); + fprintf(fd,"%c%c%c",output,output,output); + } } } } + + else + { + /* We should never get here, but if */ + /* we do, display the region as black */ + + fprintf(fd,"%c%c%c",0,0,0); + } } } + /* Display legend along bottom of image */ + x0=width/16; for (y0=0; y0<30; y0++) @@ -2572,78 +3398,63 @@ void WritePPMLR(char *filename) switch (t) { case 0: - /* Green */ - fprintf(fd,"%c%c%c",0,255,0); + fprintf(fd,"%c%c%c",255,0,0); break; case 1: - /* Pink */ - fprintf(fd,"%c%c%c",255,192,203); + fprintf(fd,"%c%c%c",255,128,0); break; case 2: - /* Cyan */ - fprintf(fd,"%c%c%c",0,255,255); + fprintf(fd,"%c%c%c",255,165,0); break; case 3: - /* Yellow */ - fprintf(fd,"%c%c%c",255,255,0); + fprintf(fd,"%c%c%c",255,206,0); break; case 4: - /* Medium Violet */ - fprintf(fd,"%c%c%c",161,131,224); + fprintf(fd,"%c%c%c",255,255,0); break; case 5: - /* Orange */ - fprintf(fd,"%c%c%c",255,165,0); + fprintf(fd,"%c%c%c",184,255,0); break; - case 6: - /* Light Green */ - fprintf(fd,"%c%c%c",193,255,193); + case 6: + fprintf(fd,"%c%c%c",0,255,0); break; - case 7: - /* Red Pink */ - fprintf(fd,"%c%c%c",255,108,108); + case 7: + fprintf(fd,"%c%c%c",0,208,0); break; case 8: - /* Green Yellow */ - fprintf(fd,"%c%c%c",173,255,47); + fprintf(fd,"%c%c%c",0,196,196); break; case 9: - /* Blanched Almond */ - fprintf(fd,"%c%c%c",255,235,184); + fprintf(fd,"%c%c%c",0,148,255); break; case 10: - /* Dark Turquoise */ - fprintf(fd,"%c%c%c",0,206,209); + fprintf(fd,"%c%c%c",80,80,255); break; case 11: - /* Tan */ - fprintf(fd,"%c%c%c",210,180,140); + fprintf(fd,"%c%c%c",0,38,255); break; case 12: - /* Magenta 1 */ - fprintf(fd,"%c%c%c",243,110,205); + fprintf(fd,"%c%c%c",142,63,255); break; case 13: - /* Gold2 */ - fprintf(fd,"%c%c%c",238,201,0); + fprintf(fd,"%c%c%c",196,54,255); break; case 14: - /* Medium Spring Green */ - fprintf(fd,"%c%c%c",0,250,154); + fprintf(fd,"%c%c%c",255,0,255); break; case 255: @@ -2652,8 +3463,7 @@ void WritePPMLR(char *filename) break; default: - /* Very Light Blue */ - fprintf(fd,"%c%c%c",244,244,255); + fprintf(fd,"%c%c%c",255,194,204); } } } @@ -2671,18 +3481,24 @@ void GraphTerrain(struct site source, struct site destination, char *name) and destination locations. "filename" 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, .gif is assumed. */ + If no extension is found, .png is assumed. */ - int x, y, z; - char filename[255], term[15], ext[15]; - FILE *fd=NULL; + int x, y, z; + char filename[255], term[30], ext[15]; + FILE *fd=NULL; ReadPath(destination,source); fd=fopen("profile.gp","wb"); for (x=0; xmaxangle) maxangle=angle; } - fprintf(fd,"%f\t%f\n",path.distance[path.length-1],refangle); - fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],refangle); + 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); fclose(fd2); @@ -2806,8 +3655,8 @@ void GraphElevation(struct site source, struct site destination, char *name) /* Default filename and output file type */ strncpy(filename,"profile\0",8); - strncpy(term,"gif\0",4); - strncpy(ext,"gif\0",4); + strncpy(term,"png\0",4); + strncpy(ext,"png\0",4); } else @@ -2831,11 +3680,11 @@ void GraphElevation(struct site source, struct site destination, char *name) } else - { /* No extension -- Default is gif */ + { /* No extension -- Default is png */ filename[x]=0; - strncpy(term,"gif\0",4); - strncpy(ext,"gif\0",4); + strncpy(term,"png\0",4); + strncpy(ext,"png\0",4); } } @@ -2846,7 +3695,7 @@ void GraphElevation(struct site source, struct site destination, char *name) strncpy(ext,"ps\0",3); else if (strncmp(ext,"ps",2)==0) - strncpy(term,"postscript\0",11); + strncpy(term,"postscript enhanced color\0",26); fprintf(stdout,"Writing \"%s.%s\"...",filename,ext); fflush(stdout); @@ -2855,12 +3704,19 @@ void GraphElevation(struct site source, struct site destination, char *name) fprintf(fd,"set grid\n"); fprintf(fd,"set yrange [%2.3f to %2.3f]\n", (-fabs(refangle)-0.25), maxangle+0.25); + fprintf(fd,"set encoding iso_8859_1\n"); fprintf(fd,"set term %s\n",term); - fprintf(fd,"set title \"SPLAT! Elevation Profile\"\n"); - fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name); - fprintf(fd,"set ylabel \"Elevation Angle Along Path Between %s and %s (degrees)\"\n",destination.name,source.name); + fprintf(fd,"set title \"SPLAT! Elevation Profile Between %s and %s (%.2f%c azimuth)\"\n",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)); + else + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination)); + + + fprintf(fd,"set ylabel \"Elevation Angle Along LOS Path Between %s and %s (degrees)\"\n",destination.name,source.name); fprintf(fd,"set output \"%s.%s\"\n",filename,ext); - fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines\n"); + 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); @@ -2880,68 +3736,209 @@ void GraphElevation(struct site source, struct site destination, char *name) fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); } -void GraphHeight(struct site source, struct site destination, char *name) +void GraphHeight(struct site source, struct site destination, char *name, double f, unsigned char n) { /* This function invokes gnuplot to generate an appropriate output file indicating the terrain profile between the source - and destination locations. What is plotted is the height of land - above or below a straight line between the receibe and transmit - sites. "filename" 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, .gif is assumed. */ - - int x, y, z; - char filename[255], term[15], ext[15]; - double a, b, c, height, refangle, cangle, maxheight=-100000.0, - minheight=100000.0; - struct site remote; - FILE *fd=NULL, *fd2=NULL; + and destination locations referenced to the line-of-sight path + between the receive and transmit sites. "filename" 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 filename[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, *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 (f) + { + lambda=9.8425e8/(f*1e6); + d=5280.0*path.distance[path.length-1]; + } + + if (n) + { + 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"); fd2=fopen("reference.gp","wb"); + fd5=fopen("curvature.gp", "wb"); - for (x=1; x0) + { + 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); + 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); + fprintf(fd2,"%f\t%f\n",path.distance[x],r); + fprintf(fd5,"%f\t%f\n",path.distance[x],height-terrain); + } + + if (f) + { + 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_zonemaxheight) maxheight=height; if (heightmaxheight) + maxheight=r; + + if (terrainmaxheight) + maxheight=r; - fprintf(fd,"%f\t%f\n",path.distance[path.length-1],0.0); - fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],0.0); + if (r=0.0) + { + fprintf(fd2,"Site location: %.4f North / %.4f West",source.lat, source.lon); + fprintf(fd2, " (%s N / ", dec2dms(source.lat)); + } + + else + { + + fprintf(fd2,"Site location: %.4f South / %.4f West",-source.lat, source.lon); + fprintf(fd2, " (%s S / ", dec2dms(source.lat)); + } + fprintf(fd2, "%s W)\n", dec2dms(source.lon)); - fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(source)); - fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",source.alt, source.alt+GetElevation(source)); + + if (metric) + { + fprintf(fd2,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(source)); + fprintf(fd2,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*source.alt,METERS_PER_FOOT*(source.alt+GetElevation(source))); + } + + else + { + fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(source)); + fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",source.alt, source.alt+GetElevation(source)); + } haavt=haat(source); if (haavt>-4999.0) - fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt); + { + if (metric) + fprintf(fd2,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt); + else + fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt); + } + + azimuth=Azimuth(source,destination); + angle1=ElevationAngle(source,destination); + angle2=ElevationAngle2(source,destination,earthradius); + + if (got_azimuth_pattern || got_elevation_pattern) + { + x=(int)rint(10.0*(10.0-angle2)); + + if (x>=0 && x<=1000) + pattern=(double)LR.antenna_pattern[(int)rint(azimuth)][x]; + + patterndB=20.0*log10(pattern); + + fprintf(fd2,"Antenna pattern between %s and %s: %.3f (%.2f dB)\n",source.name, destination.name, pattern, patterndB); + + } + + if (metric) + fprintf(fd2,"Distance to %s: %.2f kilometers\n",destination.name,METERS_PER_FOOT*Distance(source,destination)); + + else + fprintf(fd2,"Distance to %s: %.2f miles.\n",destination.name,Distance(source,destination)); + + fprintf(fd2,"Azimuth to %s: %.2f degrees\n",destination.name,azimuth); - fprintf(fd2,"Distance to %s: %.2f miles.\n",destination.name,Distance(source,destination)); - fprintf(fd2,"Azimuth to %s: %.2f degrees.\n",destination.name,Azimuth(source,destination)); + if (angle1>=0.0) + fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",destination.name,angle1); - angle=ElevationAngle(source,destination); + else + fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",destination.name,angle1); - if (angle>=0.0) - fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle); + if (angle1!=angle2) + { + if (angle2<0.0) + fprintf(fd2,"Depression"); + else + fprintf(fd2,"Elevation"); - if (angle<0.0) - fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle); + fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2); + } fprintf(fd2,"\n-------------------------------------------------------------------------\n\n"); /* Receiver */ fprintf(fd2,"Receiver site: %s\n",destination.name); - fprintf(fd2,"Site location: %.4f North / %.4f West",destination.lat, destination.lon); - fprintf(fd2, " (%s N / ", dec2dms(destination.lat)); + + if (destination.lat>=0.0) + { + fprintf(fd2,"Site location: %.4f North / %.4f West",destination.lat, destination.lon); + fprintf(fd2, " (%s N / ", dec2dms(destination.lat)); + } + + else + { + fprintf(fd2,"Site location: %.4f South / %.4f West",-destination.lat, destination.lon); + fprintf(fd2, " (%s S / ", dec2dms(destination.lat)); + } + fprintf(fd2, "%s W)\n", dec2dms(destination.lon)); - fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(destination)); - fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",destination.alt, destination.alt+GetElevation(destination)); + + if (metric) + { + fprintf(fd2,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(destination)); + fprintf(fd2,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*destination.alt, METERS_PER_FOOT*(destination.alt+GetElevation(destination))); + } + + else + { + fprintf(fd2,"Ground elevation: %.2f feet AMSL\n",GetElevation(destination)); + fprintf(fd2,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",destination.alt, destination.alt+GetElevation(destination)); + } haavt=haat(destination); if (haavt>-4999.0) - fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt); + { + if (metric) + fprintf(fd2,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt); + else + fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt); + } + + if (metric) + fprintf(fd2,"Distance to %s: %.2f kilometers\n",source.name,KM_PER_MILE*Distance(source,destination)); + + else + fprintf(fd2,"Distance to %s: %.2f miles\n",source.name,Distance(source,destination)); + + azimuth=Azimuth(destination,source); + + angle1=ElevationAngle(destination,source); + angle2=ElevationAngle2(destination,source,earthradius); + + fprintf(fd2,"Azimuth to %s: %.2f degrees.\n",source.name,azimuth); - fprintf(fd2,"Distance to %s: %.2f miles.\n",source.name,Distance(source,destination)); - fprintf(fd2,"Azimuth to %s: %.2f degrees.\n",source.name,Azimuth(destination,source)); + if (angle1>=0.0) + fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",source.name,angle1); - angle=ElevationAngle(destination,source); + else + fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",source.name,angle1); - if (angle>=0.0) - fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle); + if (angle1!=angle2) + { + if (angle2<0.0) + fprintf(fd2,"Depression"); + else + fprintf(fd2,"Elevation"); - if (angle<0.0) - fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle); + fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2); + } fprintf(fd2,"\n-------------------------------------------------------------------------\n\n"); @@ -3148,52 +4309,145 @@ void GraphLongley(struct site source, struct site destination, char *name) fprintf(fd2,")\nFraction of Situations: %.1lf%c\n",LR.conf*100.0,37); fprintf(fd2,"Fraction of Time: %.1lf%c\n",LR.rel*100.0,37); - fprintf(fd2,"\n-------------------------------------------------------------------------\n\n"); fprintf(fd2,"Analysis Results:\n\n"); - ReadPath(source, destination); /* destination=RX, source=TX */ + ReadPath(source, destination); /* source=TX, destination=RX */ + + /* Copy elevations along path into the elev_l[] array. */ - elev_l[1]=0.04*METERS_PER_MILE; + for (x=0; xcos_test_angle) + block=1; + } + + /* At this point, we have the elevation angle + to the first obstruction (if it exists). */ + } + + /* Determine path loss for each point along the + path using Longley-Rice's point_to_point mode + starting at x=2 (number_of_points = 1), the + shortest distance terrain can play a role in + path loss. */ + + elev_l[0]=y-1; /* (number of points - 1) */ + + /* Distance between elevation samples */ + elev_l[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]); point_to_point(elev_l, source.alt*METERS_PER_FOOT, - destination.alt*METERS_PER_FOOT, - LR.eps_dielect, LR.sgm_conductivity, LR.eno_ns_surfref, - LR.frq_mhz, LR.radio_climate, LR.pol, LR.conf, LR.rel, - loss, strmode, errnum); + destination.alt*METERS_PER_FOOT, LR.eps_dielect, + LR.sgm_conductivity, LR.eno_ns_surfref, LR.frq_mhz, + LR.radio_climate, LR.pol, LR.conf, LR.rel, loss, + strmode, errnum); + + if (block) + elevation=((acos(cos_test_angle))/deg2rad)-90.0; + else + elevation=((acos(cos_xmtr_angle))/deg2rad)-90.0; + + /* Integrate the antenna's radiation + pattern into the overall path loss. */ + + x=(int)rint(10.0*(10.0-elevation)); + + if (x>=0 && x<=1000) + { + pattern=(double)LR.antenna_pattern[(int)azimuth][x]; + + if (pattern!=0.0) + patterndB=20.0*log10(pattern); + } + + else + patterndB=0.0; + + total_loss=loss-patterndB; - /* Note: PASS BY REFERENCE ... loss and errnum are pass - by reference, only used in this file by this function */ + if (metric) + { + fprintf(fd,"%f\t%f\n",KM_PER_MILE*(path.distance[path.length-1]-path.distance[y]),total_loss); + fprintf(fd2,"%7.2f\t\t%7.2f\t\t %d\t%s\n",KM_PER_MILE*path.distance[y],total_loss, errnum, strmode); + } + else + { + fprintf(fd,"%f\t%f\n",path.distance[path.length-1]-path.distance[y],total_loss); + fprintf(fd2,"%7.2f\t\t%7.2f\t\t %d\t%s\n",path.distance[y],total_loss, errnum, strmode); + } - fprintf(fd,"%f\t%f\n",path.distance[path.length-1]-path.distance[x],loss); - fprintf(fd2,"%7.2f\t\t%7.2f\t\t %d\t%s\n",path.distance[x],loss, errnum, strmode); errflag|=errnum; - - if (loss>maxloss) - maxloss=loss; - if (lossmaxloss) + maxloss=total_loss; + + if (total_loss=0.0) + { + fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon); + fprintf(fd, " (%s N / ", dec2dms(xmtr.lat)); + } + + else + { + fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon); + fprintf(fd, " (%s S / ", dec2dms(xmtr.lat)); + } + fprintf(fd, "%s W)\n", dec2dms(xmtr.lon)); - fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr)); - fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr)); + + if (metric) + { + fprintf(fd,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(xmtr)); + fprintf(fd,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*xmtr.alt, METERS_PER_FOOT*(xmtr.alt+GetElevation(xmtr))); + } + + else + { + fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr)); + fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr)); + } haavt=haat(xmtr); if (haavt>-4999.0) - fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt); + { + if (metric) + fprintf(fd,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt); + else + fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt); + } + + pattern=1.0; + patterndB=0.0; + distance=Distance(xmtr,rcvr); + azimuth=Azimuth(xmtr,rcvr); + angle1=ElevationAngle(xmtr,rcvr); + angle2=ElevationAngle2(xmtr,rcvr,earthradius); + + if (got_azimuth_pattern || got_elevation_pattern) + { + x=(int)rint(10.0*(10.0-angle2)); + + if (x>=0 && x<=1000) + pattern=(double)LR.antenna_pattern[(int)rint(azimuth)][x]; + + if (pattern!=1.0) + { + fprintf(fd,"Antenna pattern toward %s: %.3f",rcvr.name,pattern); + patterndB=20.0*log10(pattern); + fprintf(fd," (%.2f dB)\n",patterndB); + } + } + + if (metric) + fprintf(fd,"Distance to %s: %.2f kilometers\n",rcvr.name,KM_PER_MILE*distance); + + else + fprintf(fd,"Distance to %s: %.2f miles\n",rcvr.name,distance); + + fprintf(fd,"Azimuth to %s: %.2f degrees\n",rcvr.name,azimuth); - fprintf(fd,"Distance to %s: %.2f miles.\n",rcvr.name,Distance(xmtr,rcvr)); - fprintf(fd,"Azimuth to %s: %.2f degrees.\n",rcvr.name,Azimuth(xmtr,rcvr)); + if (angle1>=0.0) + fprintf(fd,"Elevation angle to %s: %+.4f degrees\n",rcvr.name,angle1); - angle=ElevationAngle(xmtr,rcvr); + else + fprintf(fd,"Depression angle to %s: %+.4f degrees\n",rcvr.name,angle1); - if (angle>=0.0) - fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle); + if (angle1!=angle2) + { + if (angle2<0.0) + fprintf(fd,"Depression"); + else + fprintf(fd,"Elevation"); - if (angle<0.0) - fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle); + fprintf(fd," angle to the first obstruction: %+.4f degrees\n",angle2); + } fprintf(fd,"\n-------------------------------------------------------------------------\n\n"); /* Receiver */ - fprintf(fd,"Receiver site: %s\n",rcvr.name); - fprintf(fd,"Site location: %.4f North / %.4f West",rcvr.lat, rcvr.lon); - fprintf(fd, " (%s N / ", dec2dms(rcvr.lat)); - fprintf(fd, "%s W)\n", dec2dms(rcvr.lon)); - fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(rcvr)); - fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",rcvr.alt, rcvr.alt+GetElevation(rcvr)); + fprintf(fd,"Receiver site: %s\n",rcvr.name); + + if (rcvr.lat>=0.0) + { + fprintf(fd,"Site location: %.4f North / %.4f West",rcvr.lat, rcvr.lon); + fprintf(fd, " (%s N / ", dec2dms(rcvr.lat)); + } + + else + { + fprintf(fd,"Site location: %.4f South / %.4f West",-rcvr.lat, rcvr.lon); + fprintf(fd, " (%s S / ", dec2dms(rcvr.lat)); + } + + fprintf(fd, "%s W)\n", dec2dms(rcvr.lon)); + + if (metric) + { + fprintf(fd,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(rcvr)); + fprintf(fd,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*rcvr.alt, METERS_PER_FOOT*(rcvr.alt+GetElevation(rcvr))); + } + + else + { + fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(rcvr)); + fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",rcvr.alt, rcvr.alt+GetElevation(rcvr)); + } + + haavt=haat(rcvr); + + if (haavt>-4999.0) + { + if (metric) + fprintf(fd,"Antenna height above average terrain: %.2f meters\n",METERS_PER_FOOT*haavt); + else + fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt); + } + + azimuth=Azimuth(rcvr,xmtr); + angle1=ElevationAngle(rcvr,xmtr); + angle2=ElevationAngle2(rcvr,xmtr,earthradius); + + if (metric) + fprintf(fd,"Distance to %s: %.2f kilometers\n",xmtr.name,KM_PER_MILE*distance); + else + fprintf(fd,"Distance to %s: %.2f miles\n",xmtr.name,distance); + + fprintf(fd,"Azimuth to %s: %.2f degrees\n",xmtr.name,azimuth); + + if (angle1>=0.0) + fprintf(fd,"Elevation to %s: %+.4f degrees\n",xmtr.name,angle1); + + else + fprintf(fd,"Depression angle to %s: %+.4f degrees\n",xmtr.name,angle1); + + if (angle1!=angle2) + { + if (angle2<0.0) + fprintf(fd,"Depression"); + else + fprintf(fd,"Elevation"); + + fprintf(fd," angle to the first obstruction: %+.4f degrees\n",angle2); + + } + + fprintf(fd,"\n-------------------------------------------------------------------------\n\n"); + + if (report=='y') + { + /* Generate profile of the terrain. Create the path + from transmitter to receiver because that's the + way the original los() function did it, and going + the other way can yield slightly different results. */ + + 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); + + /* 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 the 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; + 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(fd,"SPLAT! detected obstructions at:\n\n"); + + if (site_x.lat>=0.0) + { + if (metric) + fprintf(fd,"\t%.4f N, %.4f W, %5.2f kilometers, %6.2f meters AMSL\n",site_x.lat, site_x.lon, KM_PER_MILE*(d_x/5280.0), METERS_PER_FOOT*(h_x-earthradius)); + else + fprintf(fd,"\t%.4f N, %.4f W, %5.2f miles, %6.2f feet AMSL\n",site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius); + } + + else + { + if (metric) + fprintf(fd,"\t%.4f S, %.4f W, %5.2f kilometers, %6.2f meters AMSL\n",-site_x.lat, site_x.lon, KM_PER_MILE*(d_x/5280.0), METERS_PER_FOOT*(h_x-earthradius)); + else + + fprintf(fd,"\t%.4f S, %.4f W, %5.2f miles, %6.2f feet AMSL\n",-site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius); + } + } + + 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, but don't + clutter the obstruction report. */ + + 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_fh_r_orig) + { + if (metric) + sprintf(string,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear all obstructions detected by SPLAT!\n",rcvr.name, METERS_PER_FOOT*(h_r-GetElevation(rcvr)-earthradius)); + else + sprintf(string,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear all obstructions detected by SPLAT!\n",rcvr.name, h_r-GetElevation(rcvr)-earthradius); + } + + else + sprintf(string,"\nNo obstructions to LOS path due to terrain were detected by SPLAT!\n"); + + if (f) + { + if (h_r_fpt6>h_r_orig) + { + if (metric) + sprintf(string_fpt6,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear 60%c of the first Fresnel zone.\n",rcvr.name, METERS_PER_FOOT*(h_r_fpt6-GetElevation(rcvr)-earthradius),37); + + else + sprintf(string_fpt6,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear 60%c of the first Fresnel zone.\n",rcvr.name, h_r_fpt6-GetElevation(rcvr)-earthradius,37); + } + + else + sprintf(string_fpt6,"\n60%c of the first Fresnel zone is clear.\n",37); + + if (h_r_f1>h_r_orig) + { + if (metric) + sprintf(string_f1,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear the first Fresnel zone.\n",rcvr.name, METERS_PER_FOOT*(h_r_f1-GetElevation(rcvr)-earthradius)); + + else + sprintf(string_f1,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear the first Fresnel zone.\n",rcvr.name, h_r_f1-GetElevation(rcvr)-earthradius); + + } + + else + sprintf(string_f1,"\nThe first Fresnel zone is clear.\n\n"); + } + } + + fprintf(fd,"%s",string); + + if (f) + { + fprintf(fd,"%s",string_f1); + fprintf(fd,"%s",string_fpt6); + } + + fclose(fd); + + /* Display report summary on terminal */ + + /* Line-of-sight status */ + + fprintf(stdout,"%s",string); + + if (f) + { + /* Fresnel zone status */ + + fprintf(stdout,"%s",string_f1); + fprintf(stdout,"%s",string_fpt6); + } + + fprintf(stdout, "\nObstruction report written to: \"%s\"\n",report_name); + + fflush(stdout); +} + +void SiteReport(struct site xmtr) +{ + char report_name[80]; + double terrain; + int x, azi; + FILE *fd; + + sprintf(report_name,"%s-site_report.txt",xmtr.name); + + for (x=0; report_name[x]!=0; x++) + if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47) + report_name[x]='_'; + + fd=fopen(report_name,"w"); + + fprintf(fd,"\n\t--==[ SPLAT! v%s Site Analysis Report For: %s ]==--\n\n",splat_version,xmtr.name); + + fprintf(fd,"---------------------------------------------------------------------------\n\n"); + + if (xmtr.lat>=0.0) + { + fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon); + fprintf(fd, " (%s N / ",dec2dms(xmtr.lat)); + } + + else + { + fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon); + fprintf(fd, " (%s S / ",dec2dms(xmtr.lat)); + } + + fprintf(fd, "%s W)\n",dec2dms(xmtr.lon)); + + if (metric) + { + fprintf(fd,"Ground elevation: %.2f meters AMSL\n",METERS_PER_FOOT*GetElevation(xmtr)); + fprintf(fd,"Antenna height: %.2f meters AGL / %.2f meters AMSL\n",METERS_PER_FOOT*xmtr.alt, METERS_PER_FOOT*(xmtr.alt+GetElevation(xmtr))); + } + + else + { + fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr)); + fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr)); + } + + terrain=haat(xmtr); + + if (terrain>-4999.0) + { + if (metric) + fprintf(fd,"Antenna height above average terrain: %.2f meters\n\n",METERS_PER_FOOT*terrain); + else + fprintf(fd,"Antenna height above average terrain: %.2f feet\n\n",terrain); + + /* Display the average terrain between 2 and 10 miles + from the transmitter site at azimuths of 0, 45, 90, + 135, 180, 225, 270, and 315 degrees. */ + + for (azi=0; azi<=315; azi+=45) + { + fprintf(fd,"Average terrain at %3d degrees azimuth: ",azi); + terrain=AverageTerrain(xmtr,(double)azi,2.0,10.0); + + if (terrain>-4999.0) + { + if (metric) + fprintf(fd,"%.2f meters AMSL\n",METERS_PER_FOOT*terrain); + else + fprintf(fd,"%.2f feet AMSL\n",terrain); + } + + else + fprintf(fd,"No terrain\n"); + } + } + + fprintf(fd,"\n---------------------------------------------------------------------------\n\n"); + fclose(fd); + fprintf(stdout,"\nSite analysis report written to: \"%s\"\n",report_name); +} + +void LoadTopoData(int max_lon, int min_lon, int max_lat, int min_lat) +{ + /* This function loads the SDF files required + to cover the limits of the region specified. */ + + int x, y, width, ymin, ymax; + + width=ReduceAngle(max_lon-min_lon); + + if ((max_lon-min_lon)<=180.0) + { + for (y=0; y<=width; y++) + for (x=min_lat; x<=max_lat; x++) + { + ymin=(int)(min_lon+(double)y); + + while (ymin<0) + ymin+=360; + + while (ymin>=360) + ymin-=360; + + ymax=ymin+1; + + while (ymax<0) + ymax+=360; + + while (ymax>=360) + ymax-=360; + + sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax); + LoadSDF(string); + } + } + + else + { + for (y=0; y<=width; y++) + for (x=min_lat; x<=max_lat; x++) + { + ymin=max_lon+y; + + while (ymin<0) + ymin+=360; + + while (ymin>=360) + ymin-=360; + + ymax=ymin+1; + + while (ymax<0) + ymax+=360; + + while (ymax>=360) + ymax-=360; + + sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax); + LoadSDF(string); + } + } +} + +int LoadPLI(char *filename) +{ + int error=0, max_west, min_west, max_north, min_north; + char string[80], *pointer=NULL; + double latitude=0.0, longitude=0.0, azimuth=0.0, elevation=0.0, + loss=0.0; + FILE *fd; + + fd=fopen(filename,"r"); + + if (fd!=NULL) + { + fgets(string,78,fd); + pointer=strchr(string,';'); - haavt=haat(rcvr); + if (pointer!=NULL) + *pointer=0; - if (haavt>-4999.0) - fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt); + sscanf(string,"%d, %d",&max_west, &min_west); - fprintf(fd,"Distance to %s: %.2f miles.\n",xmtr.name,Distance(xmtr,rcvr)); - fprintf(fd,"Azimuth to %s: %.2f degrees.\n",xmtr.name,Azimuth(rcvr,xmtr)); + fgets(string,78,fd); + pointer=strchr(string,';'); - angle=ElevationAngle(rcvr,xmtr); + if (pointer!=NULL) + *pointer=0; - if (angle>=0.0) - fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle); + sscanf(string,"%d, %d",&max_north, &min_north); - if (angle<0.0) - fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle); + fgets(string,78,fd); + pointer=strchr(string,';'); - fprintf(fd,"\n-------------------------------------------------------------------------\n\n"); + if (pointer!=NULL) + *pointer=0; - if (report=='y') - { - /* Write an Obstruction Report */ + LoadTopoData(max_west-1, min_west, max_north-1, min_north); - new_site=rcvr; - result=los(xmtr,rcvr); - result2=result; - result2.alt-=1; - block=result.name[0]; + fprintf(stdout,"\nReading \"%s\"... ",filename); + fflush(stdout); - if (block=='*') - fprintf(fd,"SPLAT! detected obstructions at:\n\n"); + fscanf(fd,"%lf, %lf, %lf, %lf, %lf",&latitude, &longitude, &azimuth, &elevation, &loss); - while (block=='*') + while (feof(fd)==0) { - if (result.lat!=result2.lat || result.lon!=result2.lon || result.alt!=result2.alt) - fprintf(fd,"\t%.4f N, %.4f W, %5.2f miles, %6.2f feet AMSL.\n",result.lat, result.lon, Distance(rcvr,result), result.alt); + if (loss>225.0) + loss=225.0; + + if (loss<75.0) + loss=75.0; - result2=result; - new_site.alt+=1.0; + loss-=75.0; + loss/=10.0; + loss+=1.0; - /* Can you hear me now? :-) */ + if (loss<=(double)maxdB) + OrMask(latitude,longitude,((unsigned char)(loss))<<3); - result=los(xmtr,new_site); - block=result.name[0]; + fscanf(fd,"%lf, %lf, %lf, %lf, %lf",&latitude, &longitude, &azimuth, &elevation, &loss); } - if (new_site.alt!=rcvr.alt) - sprintf(string,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear all obstructions detected by SPLAT!\n\n",rcvr.name, new_site.alt); - else - sprintf(string,"\nNo obstructions due to terrain were detected by SPLAT!\n\n"); - } - - fprintf(fd,"%s",string); + fclose(fd); - fclose(fd); + fprintf(stdout," Done!\n"); + fflush(stdout); + } - /* Display LOS status to terminal */ + else + error=1; - fprintf(stdout,"%sObstruction report written to: \"%s\"\n",string,report_name); - fflush(stdout); + return error; } -void SiteReport(struct site xmtr) +void WriteKML(struct site source, struct site destination) { - char report_name[80]; - double terrain; - int x, azi; - FILE *fd; + int x, y; + char block, report_name[80]; + double distance, rx_alt, tx_alt, cos_xmtr_angle, + azimuth, cos_test_angle, test_alt; + FILE *fd=NULL; - sprintf(report_name,"%s-site_report.txt",xmtr.name); + ReadPath(source,destination); + + sprintf(report_name,"%s-to-%s.kml",source.name,destination.name); for (x=0; report_name[x]!=0; x++) if (report_name[x]==32 || report_name[x]==17 || report_name[x]==92 || report_name[x]==42 || report_name[x]==47) @@ -3419,86 +5149,260 @@ void SiteReport(struct site xmtr) fd=fopen(report_name,"w"); - fprintf(fd,"\n\t--==[ SPLAT! v%s Site Analysis Report For: %s ]==--\n\n",splat_version,xmtr.name); + fprintf(fd,"\n"); + fprintf(fd,"\n"); + fprintf(fd,"\n",splat_version); + fprintf(fd,"\n"); + fprintf(fd,"SPLAT! Path\n"); + fprintf(fd,"1\n"); + fprintf(fd,"Path Between %s and %s\n",source.name,destination.name); + + fprintf(fd,"\n"); + fprintf(fd," %s\n",source.name); + fprintf(fd," \n"); + fprintf(fd," Transmit Site\n"); + + if (source.lat>=0.0) + fprintf(fd,"
%s North
\n",dec2dms(source.lat)); + else + fprintf(fd,"
%s South
\n",dec2dms(source.lat)); - fprintf(fd,"---------------------------------------------------------------------------\n\n"); - fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon); - fprintf(fd, " (%s N / ",dec2dms(xmtr.lat)); - fprintf(fd, "%s W)\n",dec2dms(xmtr.lon)); - fprintf(fd,"Ground elevation: %.2f feet AMSL\n",GetElevation(xmtr)); - fprintf(fd,"Antenna height: %.2f feet AGL / %.2f feet AMSL\n",xmtr.alt, xmtr.alt+GetElevation(xmtr)); + fprintf(fd,"
%s West
\n",dec2dms(source.lon)); - terrain=haat(xmtr); + azimuth=Azimuth(source,destination); + distance=Distance(source,destination); - if (terrain>-4999.0) + if (metric) + fprintf(fd,"
%.2f km",distance*KM_PER_MILE); + else + fprintf(fd,"
%.2f miles",distance); + + fprintf(fd," to %s
\n
toward an azimuth of %.2f%c
\n",destination.name,azimuth,176); + + fprintf(fd,"
\n"); + fprintf(fd," 1\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," %f,%f,30\n",(source.lon<180.0?-source.lon:360.0-source.lon),source.lat); + fprintf(fd," \n"); + fprintf(fd,"
\n"); + + fprintf(fd,"\n"); + fprintf(fd," %s\n",destination.name); + fprintf(fd," \n"); + fprintf(fd," Receive Site\n"); + + if (destination.lat>=0.0) + fprintf(fd,"
%s North
\n",dec2dms(destination.lat)); + else + fprintf(fd,"
%s South
\n",dec2dms(destination.lat)); + + fprintf(fd,"
%s West
\n",dec2dms(destination.lon)); + + + if (metric) + fprintf(fd,"
%.2f km",distance*KM_PER_MILE); + else + fprintf(fd,"
%.2f miles",distance); + + fprintf(fd," to %s
\n
toward an azimuth of %.2f%c
\n",source.name,Azimuth(destination,source),176); + + fprintf(fd,"
\n"); + fprintf(fd," 1\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," %f,%f,30\n",(destination.lon<180.0?-destination.lon:360.0-destination.lon),destination.lat); + fprintf(fd," \n"); + fprintf(fd,"
\n"); + + fprintf(fd,"\n"); + fprintf(fd,"Point-to-Point Path\n"); + fprintf(fd," 1\n"); + fprintf(fd," 0\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," \n"); + + for (x=0; x\n"); + fprintf(fd," \n"); + fprintf(fd,"\n"); + + fprintf(fd,"\n"); + fprintf(fd,"Line-of-Sight Path\n"); + fprintf(fd," 1\n"); + fprintf(fd," 0\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," \n"); + + /* Walk across the "path", indentifying obstructions along the way */ + + for (y=0; y=0 && block==0; x--) { - fprintf(fd,"Average terrain at %3d degrees azimuth: ",azi); - terrain=AverageTerrain(xmtr,(double)azi,2.0,10.0); + distance=5280.0*(path.distance[y]-path.distance[x]); + test_alt=earthradius+path.elevation[x]; - if (terrain>-4999.0) - fprintf(fd,"%.2f feet AMSL\n",terrain); - else - fprintf(fd,"No terrain\n"); + cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance); + + /* Compare these two angles to determine if + an obstruction exists. Since we're comparing + the cosines of these angles rather than + the angles themselves, the following "if" + statement is reversed from what it would + be if the actual angles were compared. */ + + if (cos_xmtr_angle>cos_test_angle) + block=1; } + + if (block) + fprintf(fd," %f,%f,-30\n",(path.lon[y]<180.0?-path.lon[y]:360.0-path.lon[y]),path.lat[y]); + else + fprintf(fd," %f,%f,5\n",(path.lon[y]<180.0?-path.lon[y]:360.0-path.lon[y]),path.lat[y]); } - fprintf(fd,"\n---------------------------------------------------------------------------\n\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd,"\n"); + + fprintf(fd," \n"); + fprintf(fd," %f\n",(source.lon<180.0?-source.lon:360.0-source.lon)); + fprintf(fd," %f\n",source.lat); + fprintf(fd," 300.0\n"); + fprintf(fd," 45.0\n"); + fprintf(fd," %f\n",azimuth); + fprintf(fd," \n"); + + fprintf(fd,"
\n"); + fprintf(fd,"
\n"); + fclose(fd); - fprintf(stdout,"\nSite analysis report written to: \"%s\"\n",report_name); + + fprintf(stdout, "KML file written to: \"%s\"\n",report_name); + + fflush(stdout); } -int main(char argc, char *argv[]) +int main(int argc, char *argv[]) { + int x, y, z=0, min_lat, min_lon, max_lat, max_lon, + rxlat, rxlon, txlat, txlon, west_min, west_max, + north_min, north_max; - int x, y, z=0; - unsigned char rxlat, rxlon, txlat, txlon, min_lat, - min_lon, max_lat, max_lon, - coverage=0, LRmap=0, - ext[20], terrain_plot=0, + unsigned char coverage=0, LRmap=0, ext[20], terrain_plot=0, elevation_plot=0, height_plot=0, longley_plot=0, cities=0, bfs=0, txsites=0, - count, west_min, west_max, north_min, north_max, - report='y'; + count, report='y', norm=0, topomap=0, geo=0, + kml=0; char mapfile[255], header[80], city_file[5][255], elevation_file[255], height_file[255], longley_file[255], terrain_file[255], - string[255], rxfile[255], - txfile[255], map=0, boundary_file[5][255]; + string[255], rxfile[255], *env=NULL, + txfile[255], map=0, boundary_file[5][255], + udt_file[255], rxsite=0, plo_filename[255], + pli_filename[255], nf=0; double altitude=0.0, altitudeLR=0.0, tx_range=0.0, rx_range=0.0, deg_range=0.0, deg_limit, - deg_range_lon, er_mult; + deg_range_lon, er_mult, freq=0.0; + struct site tx_site[4], rx_site; + FILE *fd; - sprintf(header,"\n --==[ SPLAT! v%s Terrain Analysis Software (c) 1997-2004 KD2BD ]==--\n\n", splat_version); if (argc==1) { - fprintf(stdout, "%sAvailable Options...\n\n\t-t txsite(s).qth (max of 4)\n\t-r rxsite.qth\n",header); - fprintf(stdout,"\t-c plot coverage area(s) of TX(s) based on an RX antenna at X feet AGL\n"); - fprintf(stdout,"\t-L plot path loss map of TX based on an RX antenna at X feet AGL\n"); - fprintf(stdout,"\t-s filename(s) of city/site file(s) to import (max of 5)\n"); - fprintf(stdout,"\t-b filename(s) of cartographic boundary file(s) to import (max of 5)\n"); - fprintf(stdout,"\t-p filename of terrain profile graph to plot\n"); - fprintf(stdout,"\t-e filename of terrain elevation graph to plot\n"); - fprintf(stdout,"\t-h filename of terrain height graph to plot\n"); - fprintf(stdout,"\t-l filename of Longley-Rice graph to plot\n"); - fprintf(stdout,"\t-o filename of topographic map to generate (.ppm)\n"); - fprintf(stdout,"\t-d sdf file directory path (overrides path in ~/.splat_path file)\n"); - fprintf(stdout,"\t-n no analysis, brief report\n\t-N no analysis, no report\n"); - fprintf(stdout,"\t-m earth radius multiplier\n"); - fprintf(stdout,"\t-R modify default range for -c or -L (miles)\n\n"); - + fprintf(stdout,"\n\t\t --==[ SPLAT! v%s Available Options... ]==--\n\n",splat_version); + fprintf(stdout," -t txsite(s).qth (max of 4)\n"); + fprintf(stdout," -r rxsite.qth\n"); + fprintf(stdout," -c plot coverage of TX(s) with an RX antenna at X feet/meters AGL\n"); + fprintf(stdout," -L plot path loss map of TX based on an RX at X feet/meters AGL\n"); + fprintf(stdout," -s filename(s) of city/site file(s) to import (5 max)\n"); + fprintf(stdout," -b filename(s) of cartographic boundary file(s) to import (max of 5)\n"); + fprintf(stdout," -p filename of terrain profile graph to plot\n"); + fprintf(stdout," -e filename of terrain elevation graph to plot\n"); + fprintf(stdout," -h filename of terrain height graph to plot\n"); + fprintf(stdout," -H filename of normalized terrain height graph to plot\n"); + fprintf(stdout," -l filename of Longley-Rice graph to plot\n"); + fprintf(stdout," -o filename of topographic map to generate (.ppm)\n"); + fprintf(stdout," -u filename of user-defined terrain file to import\n"); + fprintf(stdout," -d sdf file directory path (overrides path in ~/.splat_path file)\n"); + fprintf(stdout," -n no analysis, brief report\n"); + fprintf(stdout," -N no analysis, no report\n"); + fprintf(stdout," -m earth radius multiplier\n"); + fprintf(stdout," -f frequency for Fresnel zone calculation (MHz)\n"); + fprintf(stdout," -R modify default range for -c or -L (miles/kilometers)\n"); + fprintf(stdout," -db maximum loss contour to display on path loss maps (80-230 dB)\n"); + fprintf(stdout," -nf do not plot Fresnel zones in height plots\n"); + fprintf(stdout," -plo filename of path-loss output file\n"); + fprintf(stdout," -pli filename of path-loss input file\n"); + fprintf(stdout," -udt filename of user defined terrain input file\n"); + fprintf(stdout," -geo generate an Xastir .geo georeference file (with .ppm output)\n"); + fprintf(stdout," -kml generate a Google Earth .kml file (for point-to-point links)\n"); + fprintf(stdout," -metric employ metric rather than imperial units for all user I/O\n\n"); + + fprintf(stdout,"If that flew by too fast, consider piping the output through 'less':\n"); + fprintf(stdout,"\n\tsplat | less\n\n"); fprintf(stdout,"Type 'man splat', or see the documentation for more details.\n\n"); fflush(stdout); return 1; @@ -3506,6 +5410,7 @@ int main(char argc, char *argv[]) y=argc-1; + metric=0; rxfile[0]=0; txfile[0]=0; string[0]=0; @@ -3513,19 +5418,31 @@ int main(char argc, char *argv[]) elevation_file[0]=0; terrain_file[0]=0; sdf_path[0]=0; + udt_file[0]=0; path.length=0; - rx_site.lat=0.0; - rx_site.lon=0.0; + LR.frq_mhz=0.0; + rx_site.lat=91.0; + rx_site.lon=361.0; + plo_filename[0]=0; + pli_filename[0]=0; earthradius=EARTHRADIUS; + sprintf(header,"\n\t\t--==[ Welcome To SPLAT! v%s ]==--\n\n", splat_version); + + for (x=0; x<4; x++) + { + tx_site[x].lat=91.0; + tx_site[x].lon=361.0; + } + for (x=0; x230) + maxdB=230; + } + } + if (strcmp(argv[x],"-p")==0) { z=x+1; @@ -3608,7 +5551,7 @@ int main(char argc, char *argv[]) } } - if (strcmp(argv[x],"-h")==0) + if (strcmp(argv[x],"-h")==0 || strcmp(argv[x],"-H")==0) { z=x+1; @@ -3617,26 +5560,37 @@ int main(char argc, char *argv[]) strncpy(height_file,argv[z],253); height_plot=1; } + + if (strcmp(argv[x],"-H")==0) + norm=1; + else + norm=0; } if (strcmp(argv[x],"-n")==0) { - if (z<=y && argv[z][0] && argv[z][0]!='-') - { - report='n'; - map=1; - } + report='n'; + map=1; } if (strcmp(argv[x],"-N")==0) { - if (z<=y && argv[z][0] && argv[z][0]!='-'); - { - report='N'; - map=1; - } + report='N'; + map=1; } + if (strcmp(argv[x],"-metric")==0) + metric=1; + + if (strcmp(argv[x],"-geo")==0) + geo=1; + + if (strcmp(argv[x],"-kml")==0) + kml=1; + + if (strcmp(argv[x],"-nf")==0) + nf=1; + if (strcmp(argv[x],"-d")==0) { z=x+1; @@ -3658,6 +5612,7 @@ int main(char argc, char *argv[]) txsites++; z++; } + z--; } @@ -3702,6 +5657,7 @@ int main(char argc, char *argv[]) { strncpy(rxfile,argv[z],253); rx_site=LoadQTH(rxfile); + rxsite=1; } } @@ -3717,6 +5673,7 @@ int main(char argc, char *argv[]) cities++; z++; } + z--; } @@ -3732,8 +5689,41 @@ int main(char argc, char *argv[]) bfs++; z++; } + z--; } + + if (strcmp(argv[x],"-f")==0) + { + z=x+1; + + if (z<=y && argv[z][0] && argv[z][0]!='-') + { + sscanf(argv[z],"%lf",&freq); + + if (freq<20) + freq=20; + + if (freq>20e3) + freq=20e3; + } + } + + if (strcmp(argv[x],"-plo")==0) + { + z=x+1; + + if (z<=y && argv[z][0] && argv[z][0]!='-') + strncpy(plo_filename,argv[z],253); + } + + if (strcmp(argv[x],"-pli")==0) + { + z=x+1; + + if (z<=y && argv[z][0] && argv[z][0]!='-') + strncpy(pli_filename,argv[z],253); + } } /* Perform some error checking on the arguments @@ -3749,7 +5739,7 @@ int main(char argc, char *argv[]) for (x=0, y=0; x feet */ + max_range/=KM_PER_MILE; /* kilometers --> miles */ + altitude/=METERS_PER_FOOT; /* meters --> feet */ + } /* If no SDF path was specified on the command line (-d), check for a path specified in the $HOME/.splat_path file. If the @@ -3778,7 +5789,8 @@ int main(char argc, char *argv[]) if (sdf_path[0]==0) { - sprintf(string,"%s/.splat_path",getenv("HOME")); + env=getenv("HOME"); + sprintf(string,"%s/.splat_path",env); fd=fopen(string,"r"); if (fd!=NULL) @@ -3812,90 +5824,98 @@ int main(char argc, char *argv[]) fprintf(stdout,"%s",header); fflush(stdout); - x=0; - y=0; + if (pli_filename[0]) + { + y=LoadPLI(pli_filename); - min_lat=0; - max_lat=0; - min_lon=0; - max_lon=0; + for (x=0; xmax_lat) - max_lat=rxlat; + x=0; + y=0; + + min_lat=90; + max_lat=-90; - if (rxlon>max_lon) - max_lon=rxlon; + min_lon=(int)floor(tx_site[0].lon); + max_lon=(int)floor(tx_site[0].lon); for (y=0, z=0; zmax_lat) max_lat=txlat; - if (txlon>max_lon) + if (LonDiff(txlon,min_lon)<0.0) + min_lon=txlon; + + if (LonDiff(txlon,max_lon)>0.0) max_lon=txlon; } - if (min_lat!=0 && min_lon!=0 && max_lat!=0 && max_lon!=0) + if (rxsite) { - for (y=min_lon; y<=max_lon; y++) - for (x=min_lat; x<=max_lat; x++) - { - sprintf(string,"%u:%u:%u:%u",x, x+1, y, y+1); - LoadSDF(string); - } + rxlat=(int)floor(rx_site.lat); + rxlon=(int)floor(rx_site.lon); + + if (rxlatmax_lat) + max_lat=rxlat; + + if (LonDiff(rxlon,min_lon)<0.0) + min_lon=rxlon; + + if (LonDiff(rxlon,max_lon)>0.0) + max_lon=rxlon; } - if (coverage) + /* Load the required SDF files */ + + LoadTopoData(max_lon, min_lon, max_lat, min_lat); + + if (coverage | LRmap | topomap) { + if (LRmap) + txsites=1; + for (z=0; zdeg_limit) deg_range_lon=deg_limit; - north_min=(unsigned char)floor(tx_site[z].lat-deg_range); - north_max=(unsigned char)floor(tx_site[z].lat+deg_range); - west_min=(unsigned char)floor(tx_site[z].lon-deg_range_lon); - west_max=(unsigned char)floor(tx_site[z].lon+deg_range_lon); - - if (min_lat==0) - min_lat=north_min; - - else if (north_minmax_lat) - max_lat=north_max; - - if (west_max>max_lon) - max_lon=west_max; - } - - if (min_lat!=0 && min_lon!=0 && max_lat!=0 && max_lon!=0) - { - for (y=min_lon; y<=max_lon; y++) - for (x=min_lat; x<=max_lat; x++) - { - sprintf(string,"%u:%u:%u:%u",x, x+1, y, y+1); - LoadSDF(string); - } - } - } - - if (LRmap) - { - /* "Ball park" estimates used to load any additional - SDF files required to conduct this analysis. */ - - tx_range=sqrt(1.5*(tx_site[0].alt+GetElevation(tx_site[0]))); - rx_range=sqrt(1.5*altitudeLR); - /** - tx_range=sqrt(5.0*tx_site[0].alt); - rx_range=sqrt(5.0*altitudeLR); - **/ + north_min=(int)floor(tx_site[z].lat-deg_range); + north_max=(int)floor(tx_site[z].lat+deg_range); - /* deg_range determines the maximum - amount of topo data we read */ + west_min=(int)floor(tx_site[z].lon-deg_range_lon); - deg_range=(tx_range+rx_range)/69.0; + while (west_min<0) + west_min+=360; - /* max_range sets the maximum size of the - analysis. A small, non-zero amount can - be used to shrink the size of the analysis - and limit the amount of topo data read by - SPLAT! A very large number will only increase - the width of the analysis, not the size of - the map. */ + while (west_min>=360) + west_min-=360; - if (max_range==0.0) - max_range=tx_range+rx_range; + west_max=(int)floor(tx_site[z].lon+deg_range_lon); - if (max_range<(tx_range+rx_range)) - deg_range=max_range/69.0; + while (west_max<0) + west_max+=360; - /* Prevent the demand for a really wide coverage - from allocating more slots than are available - in memory. */ + while (west_max>=360) + west_max-=360; - switch (MAXSLOTS) - { - case 2: deg_limit=0.25; - break; - - case 4: deg_limit=0.5; - break; + if (north_minmax_lat) + max_lat=north_max; - case 16: deg_limit=2.0; - break; + if (LonDiff(west_min,min_lon)<0.0) + min_lon=west_min; - case 25: deg_limit=3.0; + if (LonDiff(west_max,max_lon)>0.0) + max_lon=west_max; } - if (tx_site[0].lat<70.0) - deg_range_lon=deg_range/cos(deg2rad*tx_site[0].lat); - else - deg_range_lon=deg_range/cos(deg2rad*70.0); - - /* Correct for squares in degrees not being square in miles */ - - if (deg_range>deg_limit) - deg_range=deg_limit; - - if (deg_range_lon>deg_limit) - deg_range_lon=deg_limit; - - north_min=(unsigned char)floor(tx_site[0].lat-deg_range); - north_max=(unsigned char)floor(tx_site[0].lat+deg_range); - west_min=(unsigned char)floor(tx_site[0].lon-deg_range_lon); - west_max=(unsigned char)floor(tx_site[0].lon+deg_range_lon); + /* Load any additional SDF files, if required */ - if (min_lat==0) - min_lat=north_min; - - else if (north_minmax_lat) - max_lat=north_max; + if (udt_file[0]) + LoadUDT(udt_file); - if (west_max>max_lon) - max_lon=west_max; + if (mapfile[0] && topomap==0) + map=1; - if (min_lat!=0 && min_lon!=0 && max_lat!=0 && max_lon!=0) - { - for (y=min_lon; y<=max_lon; y++) - for (x=min_lat; x<=max_lat; x++) - { - sprintf(string,"%u:%u:%u:%u",x, x+1, y, y+1); - LoadSDF(string); - } - } - } + if (freq==0.0 && nf==0) + freq=LR.frq_mhz; - if (mapfile[0]) - map=1; + else if (nf==1) + freq=0.0; - if (coverage) + if (coverage | LRmap) { for (x=0; x1) @@ -4301,3 +6230,4 @@ int main(char argc, char *argv[]) return 0; } +