X-Git-Url: https://git.gag.com/?p=debian%2Fsplat;a=blobdiff_plain;f=splat.cpp;h=844a12c3cb7e7822a291ff986847a779c14b1478;hp=3e17692aafa79b69d410cb869cc403462873f38d;hb=68a3cad00312b42922f10cc4822349802f50ed20;hpb=cae76b32deb53ddbfb94b44de132a72435f56e88 diff --git a/splat.cpp b/splat.cpp index 3e17692..844a12c 100644 --- a/splat.cpp +++ b/splat.cpp @@ -1,30 +1,26 @@ -/**************************************************************************** -* SPLAT: An RF Signal Propagation Loss and Terrain Analysis Tool * -* Last update: 31-Mar-2006 * -***************************************************************************** -* 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 * -* version. * -* * -* This program is distributed in the hope that it will useful, but WITHOUT * -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * -* for more details. * -* * -***************************************************************************** - g++ -Wall -O3 -s -lm -lbz2 -fomit-frame-pointer itm.cpp splat.cpp -o splat -*****************************************************************************/ +/****************************************************************************\ +* SPLAT!: An RF Signal Path Loss And Terrain Analysis Tool * +****************************************************************************** +* Project started in 1997 by John A. Magliacane, KD2BD * +* Last update: 10-Apr-2009 * +****************************************************************************** +* Please consult the documentation for a complete list of * +* individuals who have contributed to this project. * +****************************************************************************** +* * +* 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 * +* version. * +* * +* This program is distributed in the hope that it will useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * +* for more details. * +* * +****************************************************************************** +* g++ -Wall -O3 -s -lm -lbz2 -fomit-frame-pointer itm.cpp splat.cpp -o splat * +\****************************************************************************/ #include #include @@ -34,72 +30,152 @@ #include #include #include "fontdata.h" -#include "smallfont.h" +#include "splat.h" #define GAMMA 2.5 -#define MAXSLOTS 9 #define BZBUFFER 65536 -#if MAXSLOTS==4 -#define ARRAYSIZE 4950 +#if HD_MODE==0 + #if MAXPAGES==4 + #define ARRAYSIZE 4950 + #endif + + #if MAXPAGES==9 + #define ARRAYSIZE 10870 + #endif + + #if MAXPAGES==16 + #define ARRAYSIZE 19240 + #endif + + #if MAXPAGES==25 + #define ARRAYSIZE 30025 + #endif + + #if MAXPAGES==36 + #define ARRAYSIZE 43217 + #endif + + #if MAXPAGES==49 + #define ARRAYSIZE 58813 + #endif + + #if MAXPAGES==64 + #define ARRAYSIZE 76810 + #endif + + #define IPPD 1200 +#endif + +#if HD_MODE==1 + #if MAXPAGES==1 + #define ARRAYSIZE 5092 + #endif + + #if MAXPAGES==4 + #define ARRAYSIZE 14844 + #endif + + #if MAXPAGES==9 + #define ARRAYSIZE 32600 + #endif + + #if MAXPAGES==16 + #define ARRAYSIZE 57713 + #endif + + #if MAXPAGES==25 + #define ARRAYSIZE 90072 + #endif + + #if MAXPAGES==36 + #define ARRAYSIZE 129650 + #endif + + #if MAXPAGES==49 + #define ARRAYSIZE 176437 + #endif + + #if MAXPAGES==64 + #define ARRAYSIZE 230430 + #endif + + #define IPPD 3600 #endif -#if MAXSLOTS==9 -#define ARRAYSIZE 10870 +#ifndef PI +#define PI 3.141592653589793 #endif -#if MAXSLOTS==16 -#define ARRAYSIZE 19240 +#ifndef TWOPI +#define TWOPI 6.283185307179586 #endif -#if MAXSLOTS==25 -#define ARRAYSIZE 30025 +#ifndef HALFPI +#define HALFPI 1.570796326794896 #endif -char string[255], sdf_path[255], opened=0, *splat_version={"1.1.1"}; - -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=90, max_north=-90, min_west=360, max_west=-1, - max_elevation=-32768, min_elevation=32768, bzerror, maxdB=230; - -struct site { double lat; - double lon; - double alt; - char name[50]; - } site; - -struct path { float lat[ARRAYSIZE]; - float lon[ARRAYSIZE]; - float elevation[ARRAYSIZE]; - float 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; - } LR; - -double elev_l[ARRAYSIZE+10]; +#define DEG2RAD 1.74532925199e-02 +#define EARTHRADIUS 20902230.97 +#define METERS_PER_MILE 1609.344 +#define METERS_PER_FOOT 0.3048 +#define KM_PER_MILE 1.609344 +#define FOUR_THIRDS 1.3333333333333 + +char string[255], sdf_path[255], opened=0, gpsav=0, splat_name[10], + splat_version[6], dashes[80]; + +double earthradius, max_range=0.0, forced_erp=-1.0, dpp, ppd, + fzone_clearance=0.6, forced_freq, clutter; + +int min_north=90, max_north=-90, min_west=360, max_west=-1, ippd, mpi, + max_elevation=-32768, min_elevation=32768, bzerror, contour_threshold; + +unsigned char got_elevation_pattern, got_azimuth_pattern, metric=0, dbm=0; + +struct site { double lat; + double lon; + float alt; + char name[50]; + char filename[255]; + } 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[IPPD][IPPD]; + unsigned char mask[IPPD][IPPD]; + unsigned char signal[IPPD][IPPD]; + } dem[MAXPAGES]; + +struct LR { double eps_dielect; + double sgm_conductivity; + double eno_ns_surfref; + double frq_mhz; + double conf; + double rel; + double erp; + int radio_climate; + int pol; + float antenna_pattern[361][1001]; + } LR; + +struct region { unsigned char color[32][3]; + int level[32]; + int levels; + } region; + +double elev[ARRAYSIZE+10]; void point_to_point(double elev[], double tht_m, double rht_m, double eps_dielect, double sgm_conductivity, double eno_ns_surfref, @@ -129,21 +205,49 @@ int ReduceAngle(double angle) double temp; - temp=acos(cos(angle*deg2rad)); + temp=acos(cos(angle*DEG2RAD)); + + return (int)rint(temp/DEG2RAD); +} + +double LonDiff(double lon1, double 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. */ + + double diff; + + diff=lon1-lon2; + + if (diff<=-180.0) + diff+=360.0; + + if (diff>=180.0) + diff-=360.0; - return (int)rint(temp/deg2rad); + return diff; } 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); @@ -161,10 +265,42 @@ char *dec2dms(double decimal) seconds=59; string[0]=0; - sprintf(string,"%d%c %d\' %d\"", degrees, 176, minutes, seconds); + snprintf(string,250,"%d%c %d\' %d\"", degrees*sign, 176, minutes, seconds); return (string); } +int PutMask(double lat, double lon, int value) +{ + /* Lines, text, markings, and coverage areas are stored in a + mask that is combined with topology data when topographic + maps are generated by SPLAT!. This function sets and resets + bits in the mask based on the latitude and longitude of the + area pointed to. */ + + int x, y, indx; + char found; + + for (indx=0, found=0; indx=0 && x<=mpi && y>=0 && y<=mpi) + found=1; + else + indx++; + } + + if (found) + { + dem[indx].mask[x][y]=value; + return ((int)dem[indx].mask[x][y]); + } + + else + return -1; +} + int OrMask(double lat, double lon, int value) { /* Lines, text, markings, and coverage areas are stored in a @@ -173,26 +309,24 @@ 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; + char found; - minlat=(int)floor(lat); - minlon=(int)floor(lon); + for (indx=0, found=0; indx=0 && x<=mpi && y>=0 && y<=mpi) found=1; else indx++; + } if (found) { - x=(int)(1199.0*(lat-floor(lat))); - y=(int)(1199.0*(lon-floor(lon))); - dem[indx].mask[x][y]|=value; - - return (dem[indx].mask[x][y]); + return ((int)dem[indx].mask[x][y]); } else @@ -207,35 +341,115 @@ int GetMask(double lat, double lon) return (OrMask(lat,lon,0)); } +int PutSignal(double lat, double lon, unsigned char signal) +{ + /* This function writes a signal level (0-255) + at the specified location for later recall. */ + + int x, y, indx; + char found; + + for (indx=0, found=0; indx=0 && x<=mpi && y>=0 && y<=mpi) + found=1; + else + indx++; + } + + if (found) + { + dem[indx].signal[x][y]=signal; + return (dem[indx].signal[x][y]); + } + + else + return 0; +} + +unsigned char GetSignal(double lat, double lon) +{ + /* This function reads the signal level (0-255) at the + specified location that was previously written by the + complimentary PutSignal() function. */ + + int x, y, indx; + char found; + + for (indx=0, found=0; indx=0 && x<=mpi && y>=0 && y<=mpi) + found=1; + else + indx++; + } + + if (found) + return (dem[indx].signal[x][y]); + else + return 0; +} + double GetElevation(struct site location) { /* This function returns the elevation (in feet) of any 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; + double elevation; + + for (indx=0, found=0; indx=0 && x<=mpi && y>=0 && y<=mpi) + found=1; + else + indx++; + } - elevation=-5000.0; + if (found) + elevation=3.28084*dem[indx].data[x][y]; + else + elevation=-5000.0; + + return elevation; +} - minlat=(int)floor(location.lat); - minlon=(int)floor(location.lon); +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. */ - x=(int)(1199.0*(location.lat-floor(location.lat))); - y=(int)(1199.0*(location.lon-floor(location.lon))); + char found; + int x, y, indx; - for (indx=0, found=0; indx=0 && x<=mpi && y>=0 && y<=mpi) found=1; - } + else + indx++; } - - return elevation; + + if (found) + dem[indx].data[x][y]+=(short)rint(height); + + return found; } double Distance(struct site site1, struct site site2) @@ -243,12 +457,12 @@ double Distance(struct site site1, struct site site2) /* This function returns the great circle distance in miles between any two site locations. */ - double lat1, lon1, lat2, lon2, distance; + double lat1, lon1, lat2, lon2, distance; - lat1=site1.lat*deg2rad; - lon1=site1.lon*deg2rad; - lat2=site2.lat*deg2rad; - lon2=site2.lon*deg2rad; + lat1=site1.lat*DEG2RAD; + lon1=site1.lon*DEG2RAD; + lat2=site2.lat*DEG2RAD; + lon2=site2.lon*DEG2RAD; distance=3959.0*acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos((lon1)-(lon2))); @@ -260,14 +474,14 @@ double Azimuth(struct site source, struct site destination) /* This function returns the azimuth (in degrees) to the destination as seen from the location of the source. */ - double dest_lat, dest_lon, src_lat, src_lon, - beta, azimuth, diff, num, den, fraction; + double dest_lat, dest_lon, src_lat, src_lon, + beta, azimuth, diff, num, den, fraction; - dest_lat=destination.lat*deg2rad; - dest_lon=destination.lon*deg2rad; + dest_lat=destination.lat*DEG2RAD; + dest_lon=destination.lon*DEG2RAD; - src_lat=source.lat*deg2rad; - src_lon=source.lon*deg2rad; + src_lat=source.lat*DEG2RAD; + src_lon=source.lon*DEG2RAD; /* Calculate Surface Distance */ @@ -304,13 +518,13 @@ double Azimuth(struct site source, struct site destination) if (diff>0.0) azimuth=TWOPI-azimuth; - return (azimuth/deg2rad); + return (azimuth/DEG2RAD); } -double ElevationAngle(struct site local, struct site remote) +double ElevationAngle(struct site source, struct site destination) { /* This function returns the angle of elevation (in degrees) - of the remote location as seen from the local site. + of the destination as seen from the source location. A positive result represents an angle of elevation (uptilt), while a negative result represents an angle of depression (downtilt), as referenced to a normal to the center of @@ -318,10 +532,10 @@ double ElevationAngle(struct site local, struct site remote) register double a, b, dx; - a=GetElevation(remote)+remote.alt+earthradius; - b=GetElevation(local)+local.alt+earthradius; + a=GetElevation(destination)+destination.alt+earthradius; + b=GetElevation(source)+source.alt+earthradius; - dx=5280.0*Distance(local,remote); + dx=5280.0*Distance(source,destination); /* Apply the Law of Cosines */ @@ -331,22 +545,62 @@ double ElevationAngle(struct site local, struct site remote) void ReadPath(struct site source, struct site destination) { /* This function generates a sequence of latitude and - longitude positions between a source location and - a destination along a great circle path, and stores - elevation and distance information for points along - that path in the "path" structure for later use. */ - - int c; - double azimuth, distance, lat1, lon1, beta, - den, num, lat2, lon2, total_distance; - struct site tempsite; - - lat1=source.lat*deg2rad; - lon1=source.lon*deg2rad; - azimuth=Azimuth(source,destination)*deg2rad; + longitude positions between source and destination + locations along a great circle path, and stores + elevation and distance information for points + along that path in the "path" structure. */ + + int c; + double azimuth, distance, lat1, lon1, beta, den, num, + lat2, lon2, total_distance, dx, dy, path_length, + miles_per_sample, samples_per_radian=68755.0; + struct site tempsite; + + lat1=source.lat*DEG2RAD; + lon1=source.lon*DEG2RAD; + + lat2=destination.lat*DEG2RAD; + lon2=destination.lon*DEG2RAD; + + if (ppd==1200.0) + samples_per_radian=68755.0; + + if (ppd==3600.0) + samples_per_radian=206265.0; + + azimuth=Azimuth(source,destination)*DEG2RAD; + total_distance=Distance(source,destination); - for (distance=0, c=0; distance<=total_distance; distance+=0.04) + if (total_distance>(30.0/ppd)) /* > 0.5 pixel distance */ + { + dx=samples_per_radian*acos(cos(lon1-lon2)); + dy=samples_per_radian*acos(cos(lat1-lat2)); + + path_length=sqrt((dx*dx)+(dy*dy)); /* Total number of samples */ + + miles_per_sample=total_distance/path_length; /* Miles per sample */ + } + + else + { + c=0; + dx=0.0; + dy=0.0; + path_length=0.0; + miles_per_sample=0.0; + total_distance=0.0; + + lat1=lat1/DEG2RAD; + lon1=lon1/DEG2RAD; + + path.lat[c]=lat1; + path.lon[c]=lon1; + path.elevation[c]=GetElevation(source); + path.distance[c]=0.0; + } + + for (distance=0.0, c=0; (total_distance!=0.0 && distance<=total_distance && cHALFPI+lat1)) - lon2=lon1+PI; + lon2=lon1+PI; else if (fabs(num/den)>1.0) - lon2=lon1; + lon2=lon1; else { @@ -376,19 +630,15 @@ void ReadPath(struct site source, struct site destination) while (lon2>TWOPI) lon2-=TWOPI; - lat2=lat2/deg2rad; - lon2=lon2/deg2rad; - - if (c=cos_test_angle) + { + block=1; + first_obstruction_angle=((acos(cos_test_angle))/DEG2RAD)-90.0; + } + } + + if (block) + elevation=first_obstruction_angle; + + else + elevation=((acos(cos_xmtr_angle))/DEG2RAD)-90.0; + + path=temp; + + return elevation; +} + double AverageTerrain(struct site source, double azimuthx, double start_distance, double end_distance) { /* This function returns the average terrain calculated in @@ -418,19 +737,19 @@ 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; + lat1=source.lat*DEG2RAD; + lon1=source.lon*DEG2RAD; /* Generate a path of elevations between the source location and the remote location provided. */ beta=end_distance/3959.0; - azimuth=deg2rad*azimuthx; + azimuth=DEG2RAD*azimuthx; lat2=asin(sin(lat1)*cos(beta)+cos(azimuth)*sin(beta)*cos(lat1)); num=cos(beta)-(sin(lat1)*sin(lat2)); @@ -440,10 +759,10 @@ double AverageTerrain(struct site source, double azimuthx, double start_distance lon2=lon1+PI; else if (azimuth==HALFPI && (beta>HALFPI+lat1)) - lon2=lon1+PI; + lon2=lon1+PI; else if (fabs(num/den)>1.0) - lon2=lon1; + lon2=lon1; else { @@ -459,8 +778,8 @@ double AverageTerrain(struct site source, double azimuthx, double start_distance while (lon2>TWOPI) lon2-=TWOPI; - lat2=lat2/deg2rad; - lon2=lon2/deg2rad; + lat2=lat2/DEG2RAD; + lon2=lon2/DEG2RAD; destination.lat=lat2; destination.lon=lon2; @@ -488,7 +807,7 @@ double AverageTerrain(struct site source, double azimuthx, double start_distance { if (path.distance[c]>=start_distance) { - terrain+=path.elevation[c]; + terrain+=(path.elevation[c]==0.0?path.elevation[c]:path.elevation[c]+clutter); samples++; } } @@ -509,9 +828,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, @@ -541,27 +860,6 @@ 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 @@ -579,30 +877,30 @@ void PlaceMarker(struct site location) double x, y, lat, lon, textx=0.0, texty=0.0, xmin, xmax, ymin, ymax, p1, p3, p6, p8, p12, p16, p24, label_length; - xmin=min_north; - xmax=max_north; - ymin=min_west; - ymax=max_west; + xmin=(double)min_north; + xmax=(double)max_north; + ymin=(double)min_west; + ymax=(double)max_west; lat=location.lat; lon=location.lon; - - if (latxmin && (LonDiff(lon,ymax)<0.0) && (LonDiff(lon,ymin)>0.0)) + if (lat=xmin && (LonDiff(lon,ymax)<=0.0) && (LonDiff(lon,ymin)>=dpp)) { - p1=1.0/1200.0; - p3=3.0/1200.0; - p6=6.0/1200.0; - p8=8.0/1200.0; - p12=12.0/1200.0; - p16=16.0/1200.0; - p24=24.0/1200.0; + p1=1.0/ppd; + p3=3.0/ppd; + p6=6.0/ppd; + p8=8.0/ppd; + p12=12.0/ppd; + p16=16.0/ppd; + p24=24.0/ppd; + ok2print=0; occupied=0; /* 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; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1) + for (a=0, x=lat-p3; (x<=xmax && x>=xmin && a<7); x+=p1, a++) + for (b=0, y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=dpp) && b<7; y+=p1, b++) occupied|=(GetMask(x,y)&2); if (occupied==0) @@ -614,7 +912,7 @@ void PlaceMarker(struct site location) label_length=p1*(double)(strlen(location.name)<<3); - if ((LonDiff(lon+label_length,ymax)<=0.0) && (LonDiff(lon-label_length,ymin)>=0.0)) + if ((LonDiff(lon+label_length,ymax)<=0.0) && (LonDiff(lon-label_length,ymin)>=dpp)) { /* Default: Centered Text */ @@ -679,7 +977,7 @@ void PlaceMarker(struct site location) if (ok2print==0) { - if (LonDiff(lon-label_length,ymin)>=0.0) + if (LonDiff(lon-label_length,ymin)>=dpp) { /* Position Text To The Right Of The Marker */ @@ -750,8 +1048,8 @@ void PlaceMarker(struct site location) x=textx; y=texty; - - for (a=0; a<16 && ok2print; a++) + + for (a=0; a<16; a++) { for (b=0; b<(int)strlen(location.name); b++) { @@ -768,9 +1066,9 @@ void PlaceMarker(struct site location) /* Draw Square Marker Centered On Location Specified */ - - for (x=lat-p3; (x<=xmax && x>=xmin && x<=lat+p3); x+=p1) - for (y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=0.0) && (LonDiff(y,lon+p3)<=0.0); y+=p1) + + for (a=0, x=lat-p3; (x<=xmax && x>=xmin && a<7); x+=p1, a++) + for (b=0, y=lon-p3; (LonDiff(y,ymax)<=0.0) && (LonDiff(y,ymin)>=dpp) && b<7; y+=p1, b++) OrMask(x,y,2); } } @@ -788,9 +1086,9 @@ double ReadBearing(char *input) embedded within the numbers expressed in the input string. Decimal seconds are permitted. */ - double seconds, bearing=0.0; - char string[20]; - int a, b, length, degrees, minutes; + 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. */ @@ -800,7 +1098,7 @@ double ReadBearing(char *input) for (a=0, b=0; a360.0 || bearing<-90.0) + if (bearing>360.0 || bearing<-360.0) bearing=0.0; return bearing; @@ -844,31 +1148,34 @@ 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], *s=NULL; + struct site tempsite; + FILE *fd=NULL; - for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++) - qthfile[x]=filename[x]; + x=strlen(filename); + strncpy(qthfile, filename, 254); + + if (qthfile[x-3]!='q' || qthfile[x-2]!='t' || qthfile[x-1]!='h') + { + if (x>249) + qthfile[249]=0; - qthfile[x]='.'; - qthfile[x+1]='q'; - qthfile[x+2]='t'; - qthfile[x+3]='h'; - qthfile[x+4]=0; + strncat(qthfile,".qth\0",5); + } tempsite.lat=91.0; tempsite.lon=361.0; tempsite.alt=0.0; tempsite.name[0]=0; + tempsite.filename[0]=0; fd=fopen(qthfile,"r"); if (fd!=NULL) { /* Site Name */ - fgets(string,49,fd); + s=fgets(string,49,fd); /* Strip and/or from end of site name */ @@ -877,15 +1184,18 @@ struct site LoadQTH(char *filename) tempsite.name[x]=0; /* Site Latitude */ - fgets(string,49,fd); + s=fgets(string,49,fd); tempsite.lat=ReadBearing(string); /* Site Longitude */ - fgets(string,49,fd); + s=fgets(string,49,fd); tempsite.lon=ReadBearing(string); + if (tempsite.lon<0.0) + tempsite.lon+=360.0; + /* Antenna Height */ - fgets(string,49,fd); + s=fgets(string,49,fd); fclose(fd); /* Remove and/or from antenna height string */ @@ -900,112 +1210,487 @@ struct site LoadQTH(char *filename) must be converted to feet before exiting. */ for (x=0; string[x]!='M' && string[x]!='m' && string[x]!=0 && x<48; x++); + 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); } + + for (x=0; x<254 && qthfile[x]!=0; x++) + tempsite.filename[x]=qthfile[x]; + + tempsite.filename[x]=0; } 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. */ + /* 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, *s=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=0.0, tilt_azimuth, tilt_increment, sum; + FILE *fd=NULL; + unsigned char read_count[10001]; - 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; filename[x]!='.' && filename[x]!=0 && x<250; x++) + { + azfile[x]=filename[x]; + elfile[x]=filename[x]; + } - for (x=0; name[x]!='.' && name[x]!=0 && x<250; x++) - sdf_file[x]=name[x]; + azfile[x]='.'; + azfile[x+1]='a'; + azfile[x+2]='z'; + azfile[x+3]=0; - sdf_file[x]=0; + elfile[x]='.'; + elfile[x+1]='e'; + elfile[x+2]='l'; + elfile[x+3]=0; - /* Parse filename for minimum latitude and longitude values */ + rotation=0.0; - sscanf(sdf_file,"%d:%d:%d:%d",&minlat,&maxlat,&minlon,&maxlon); + got_azimuth_pattern=0; + got_elevation_pattern=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]++; + } + + s=fgets(string,254,fd); + pointer=strchr(string,';'); + + if (pointer!=NULL) + *pointer=0; + + sscanf(string,"%f %f",&az, &litude); + + } while (feof(fd)==0); + + fclose(fd); + + + /* Handle 0=360 degree ambiguity */ + + if ((read_count[0]==0) && (read_count[360]!=0)) + { + read_count[0]=read_count[360]; + azimuth[0]=azimuth[360]; + } + + if ((read_count[0]!=0) && (read_count[360]==0)) + { + read_count[360]=read_count[0]; + azimuth[360]=azimuth[0]; + } + + /* Average pattern values in case more than + one was read for each degree of azimuth. */ + + for (x=0; x<=360; x++) + { + if (read_count[x]>1) + azimuth[x]/=(float)read_count[x]; + } + + /* Interpolate missing azimuths + to completely fill the array */ + + last_index=-1; + next_index=-1; + + for (x=0; x<=360; 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=azimuth[last_index]; + valid2=azimuth[next_index]; + + span=next_index-last_index; + delta=(valid2-valid1)/(float)span; + + for (y=last_index+1; y=360) + y-=360; + + azimuth_pattern[y]=azimuth[x]; + } + + azimuth_pattern[360]=azimuth_pattern[0]; + + got_azimuth_pattern=255; + } + + /* Read and process .el file */ + + fd=fopen(elfile,"r"); + + if (fd!=NULL) + { + for (x=0; x<=10000; x++) + { + el_pattern[x]=0.0; + read_count[x]=0; + } + + /* Read mechanical tilt (degrees) and + tilt azimuth in degrees measured + clockwise from true North. */ + + s=fgets(string,254,fd); + pointer=strchr(string,';'); + + if (pointer!=NULL) + *pointer=0; + + sscanf(string,"%f %f",&mechanical_tilt, &tilt_azimuth); + + /* Read elevation (degrees) and corresponding + normalized field radiation pattern amplitude + (0.0 to 1.0) until EOF is reached. */ + + s=fgets(string,254,fd); + pointer=strchr(string,';'); + + 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]++; + } + + s=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_page=0, line[20], sdf_file[255], + path_plus_name[255], *s=NULL; + 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; @@ -1026,13 +1711,13 @@ int LoadSDF_SDF(char *name) max_north=dem[indx].max_north; else if (dem[indx].max_north>max_north) - max_north=dem[indx].max_north; + max_north=dem[indx].max_north; if (min_north==90) min_north=dem[indx].min_north; else if (dem[indx].min_north=0 && indx=0 && indxdem[indx].max_el) dem[indx].max_el=data; @@ -1251,13 +1940,13 @@ int LoadSDF_BZ(char *name) max_north=dem[indx].max_north; else if (dem[indx].max_north>max_north) - max_north=dem[indx].max_north; + max_north=dem[indx].max_north; if (min_north==90) min_north=dem[indx].min_north; else if (dem[indx].min_north=0 && indx=0 && indx0) dem[indx].min_el=0; @@ -1388,13 +2082,13 @@ char LoadSDF(char *name) max_north=dem[indx].max_north; else if (dem[indx].max_north>max_north) - max_north=dem[indx].max_north; + max_north=dem[indx].max_north; if (min_north==90) min_north=dem[indx].min_north; else if (dem[indx].min_north and/or from antenna height string */ - lat0=lat1; - lon0=lon1; + for (i=0; str[2][i]!=13 && str[2][i]!=10 && str[2][i]!=0; i++); - fgets(string,78,fd); + str[2][i]=0; - } while (strncmp(string,"END",3)!=0 && feof(fd)==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. */ - fgets(string,78,fd); + for (i=0; str[2][i]!='M' && str[2][i]!='m' && str[2][i]!=0 && i<48; i++); - } while (strncmp(string,"END",3)!=0 && feof(fd)==0); + if (str[2][i]=='M' || str[2][i]=='m') + { + str[2][i]=0; + height=rint(atof(str[2])); + } - fclose(fd); + else + { + str[2][i]=0; + height=rint(METERS_PER_FOOT*atof(str[2])); + } - fprintf(stdout,"Done!\n"); - fflush(stdout); - } + if (height>0.0) + fprintf(fd2,"%d, %d, %f\n",(int)rint(latitude/dpp), (int)rint(longitude/dpp), height); - else - fprintf(stderr,"*** ERROR: \"%s\": not found!\n",filename); -} + s=fgets(input,78,fd1); -void ReadLRParm(char *txsite_filename) -{ + pointer=strchr(input,';'); + + if (pointer!=NULL) + *pointer=0; + } + + fclose(fd1); + fclose(fd2); + close(fd); + + fprintf(stdout,"Done!"); + fflush(stdout); + + fd1=fopen(tempname,"r"); + fd2=fopen(tempname,"r"); + + y=0; + + n=fscanf(fd1,"%d, %d, %lf", &xpix, &ypix, &height); + + do + { + x=0; + z=0; + + n=fscanf(fd2,"%d, %d, %lf", &tempxpix, &tempypix, &tempheight); + + do + { + if (x>y && xpix==tempxpix && ypix==tempypix) + { + z=1; /* Dupe! */ + + if (tempheight>height) + height=tempheight; + } + + else + { + n=fscanf(fd2,"%d, %d, %lf", &tempxpix, &tempypix, &tempheight); + x++; + } + + } while (feof(fd2)==0 && z==0); + + if (z==0) /* No duplicate found */ + AddElevation(xpix*dpp, ypix*dpp, height); + + n=fscanf(fd1,"%d, %d, %lf", &xpix, &ypix, &height); + y++; + + rewind(fd2); + + } while (feof(fd1)==0); + + fclose(fd1); + fclose(fd2); + unlink(tempname); + } + + else + fprintf(stderr,"\n*** ERROR: \"%s\": not found!",filename); + + fprintf(stdout,"\n"); +} + +void LoadBoundaries(char *filename) +{ + /* This function reads Cartographic Boundary Files available from + the U.S. Census Bureau, and plots the data contained in those + files on the PPM Map generated by SPLAT!. Such files contain + the coordinates that describe the boundaries of cities, + counties, and states. */ + + int x; + double lat0, lon0, lat1, lon1; + char string[80], *s=NULL; + struct site source, destination; + FILE *fd=NULL; + + fd=fopen(filename,"r"); + + if (fd!=NULL) + { + s=fgets(string,78,fd); + + fprintf(stdout,"\nReading \"%s\"... ",filename); + fflush(stdout); + + do + { + s=fgets(string,78,fd); + sscanf(string,"%lf %lf", &lon0, &lat0); + s=fgets(string,78,fd); + + do + { + sscanf(string,"%lf %lf", &lon1, &lat1); + + source.lat=lat0; + source.lon=(lon0>0.0 ? 360.0-lon0 : -lon0); + destination.lat=lat1; + destination.lon=(lon1>0.0 ? 360.0-lon1 : -lon1); + + ReadPath(source,destination); + + for (x=0; x=20.0 && forced_freq<=20000.0) + LR.frq_mhz=forced_freq; + + if (ok) + LoadPAT(filename); } - if (fd==NULL) + if (fd==NULL && forced_read) { - /* Create a "splat.lrp" file since one - could not be successfully loaded. */ + /* Assign some default parameters + for use in this run. */ + + LR.eps_dielect=15.0; + LR.sgm_conductivity=0.005; + LR.eno_ns_surfref=301.0; + LR.frq_mhz=300.0; + LR.radio_climate=5; + LR.pol=0; + LR.conf=0.50; + LR.rel=0.50; + LR.erp=0.0; + + /* Write them to a "splat.lrp" file. */ 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,"%.2f\t; Fraction of time\n",LR.rel); + fprintf(outfile,"%.2f\t; Transmitter Effective Radiated Power in Watts or dBm (optional)\n",LR.erp); fprintf(outfile,"\nPlease consult SPLAT! documentation for the meaning and use of this data.\n"); fclose(outfile); - fprintf(stderr,"\n%c*** There were problems reading your \"%s\" file! ***\nA \"splat.lrp\" file was written to your directory with default data.\n",7,filename); - } - - if (fd==NULL || ok==0) - 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); + return_value=1; - 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. + fprintf(stderr,"\n\n%c*** There were problems reading your \"%s\" file! ***\nA \"splat.lrp\" file was written to your directory with default data.\n",7,filename); + } - 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. */ + else if (forced_read==0) + return_value=0; - for (x=path.length-1, block=0; x>0 && block==0; x--) + if (forced_read && (fd==NULL || ok==0)) { - /* 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]; + LR.eps_dielect=15.0; + LR.sgm_conductivity=0.005; + LR.eno_ns_surfref=301.0; + LR.frq_mhz=300.0; + LR.radio_climate=5; + LR.pol=0; + LR.conf=0.50; + LR.rel=0.50; + LR.erp=0.0; - /* 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]='*'; - } - } + fprintf(stderr,"Longley-Rice default parameters have been assumed for this analysis.\n"); - if (block==0) - { - blockage.lat=0.0; - blockage.lon=0.0; - blockage.alt=0.0; - blockage.name[0]=' '; + return_value=1; } - return blockage; + return (return_value); } void PlotPath(struct site source, struct site destination, char mask_value) @@ -1907,7 +2714,7 @@ void PlotPath(struct site source, struct site destination, char mask_value) for (x=y, block=0; x>=0 && block==0; x--) { distance=5280.0*(path.distance[y]-path.distance[x]); - test_alt=earthradius+path.elevation[x]; + test_alt=earthradius+(path.elevation[x]==0.0?path.elevation[x]:path.elevation[x]+clutter); cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance); @@ -1918,7 +2725,7 @@ void PlotPath(struct site source, struct site destination, char mask_value) statement is reversed from what it would be if the actual angles were compared. */ - if (cos_xmtr_angle>cos_test_angle) + if (cos_xmtr_angle>=cos_test_angle) block=1; } @@ -1928,59 +2735,248 @@ 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, unsigned char mask_value, 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. */ + + int x, y, ifs, ofs, errnum; + char block=0, strmode[100]; + double loss, azimuth, pattern=0.0, + xmtr_alt, dest_alt, xmtr_alt2, dest_alt2, + cos_rcvr_angle, cos_test_angle=0.0, test_alt, + elevation=0.0, distance=0.0, four_thirds_earth, + field_strength=0.0, rxp, dBm; + struct site temp; ReadPath(source,destination); - elev_l[1]=0.04*METERS_PER_MILE; - for (x=0; x225.0) - loss=225.0; + if (cos_rcvr_angle>1.0) + cos_rcvr_angle=1.0; - if (loss<75.0) - loss=75.0; + if (cos_rcvr_angle<-1.0) + cos_rcvr_angle=-1.0; - loss-=75.0; - loss/=10.0; - loss+=1.0; - - OrMask(path.lat[y],path.lon[y],((unsigned char)(loss))<<3); - } + if (got_elevation_pattern || fd!=NULL) + { + /* Determine the elevation angle to the first obstruction + along the path IF elevation pattern data is available + or an output (.ano) file has been designated. */ + + for (x=2, block=0; (x1.0) + cos_test_angle=1.0; + + if (cos_test_angle<-1.0) + cos_test_angle=-1.0; + + /* 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 sense of the + following "if" statement is reversed from + what it would be if the angles themselves + were compared. */ + + if (cos_rcvr_angle>=cos_test_angle) + block=1; + } + + if (block) + elevation=((acos(cos_test_angle))/DEG2RAD)-90.0; + else + elevation=((acos(cos_rcvr_angle))/DEG2RAD)-90.0; + } + + /* 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[0]=y-1; /* (number of points - 1) */ + + /* Distance between elevation samples */ + + elev[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]); + + point_to_point(elev,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); + + temp.lat=path.lat[y]; + temp.lon=path.lon[y]; + + azimuth=(Azimuth(source,temp)); + + if (fd!=NULL) + fprintf(fd,"%.7f, %.7f, %.3f, %.3f, ",path.lat[y], path.lon[y], azimuth, elevation); + + /* If ERP==0, write path loss to alphanumeric + output file. Otherwise, write field strength + or received power level (below), as appropriate. */ + + if (fd!=NULL && LR.erp==0.0) + fprintf(fd,"%.2f",loss); + + /* Integrate the antenna's radiation + pattern into the overall path loss. */ - else if (GetMask(path.lat[y],path.lon[y])==0 && 0.04*y>max_range) - OrMask(path.lat[y],path.lon[y],1); + 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 (LR.erp!=0.0) + { + if (dbm) + { + /* dBm is based on EIRP (ERP + 2.14) */ + + rxp=LR.erp/(pow(10.0,(loss-2.14)/10.0)); + + dBm=10.0*(log10(rxp*1000.0)); + + if (fd!=NULL) + fprintf(fd,"%.3f",dBm); + + /* Scale roughly between 0 and 255 */ + + ifs=200+(int)rint(dBm); + + if (ifs<0) + ifs=0; + + if (ifs>255) + ifs=255; + + ofs=GetSignal(path.lat[y],path.lon[y]); + + if (ofs>ifs) + ifs=ofs; + + PutSignal(path.lat[y],path.lon[y],(unsigned char)ifs); + } + + else + { + field_strength=(139.4+(20.0*log10(LR.frq_mhz))-loss)+(10.0*log10(LR.erp/1000.0)); + + ifs=100+(int)rint(field_strength); + + if (ifs<0) + ifs=0; + + if (ifs>255) + ifs=255; + + ofs=GetSignal(path.lat[y],path.lon[y]); + + if (ofs>ifs) + ifs=ofs; + + PutSignal(path.lat[y],path.lon[y],(unsigned char)ifs); + + if (fd!=NULL) + fprintf(fd,"%.3f",field_strength); + } + } + + else + { + if (loss>255) + ifs=255; + else + ifs=(int)rint(loss); + + ofs=GetSignal(path.lat[y],path.lon[y]); + + if (ofs0.0) + fprintf(stdout," and %.2f %s of ground clutter",metric?clutter*METERS_PER_FOOT:clutter,metric?"meters":"feet"); + + fprintf(stdout,"...\n\n 0%c to 25%c ",37,37); fflush(stdout); - /* 18.75=1200 pixels/degree divided by 64 loops - per progress indicator symbol (.oOo) printed. */ + /* th=pixels/degree divided by 64 loops per + progress indicator symbol (.oOo) printed. */ + + th=ppd/64.0; + + z=(int)(th*ReduceAngle(max_west-min_west)); - z=(int)(18.75*ReduceAngle(max_west-min_west)); + minwest=dpp+(double)min_west; + maxnorth=(double)max_north-dpp; - for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) + for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y)) { if (lon>=360.0) lon-=360.0; @@ -2048,9 +3047,9 @@ void PlotCoverage(struct site source, double altitude) fprintf(stdout,"\n25%c to 50%c ",37,37); fflush(stdout); - z=(int)(18.75*(max_north-min_north)); + z=(int)(th*(double)(max_north-min_north)); - for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel) + for (lat=maxnorth, x=0, y=0; lat>=(double)min_north; y++, lat=maxnorth-(dpp*(double)y)) { edge.lat=lat; edge.lon=min_west; @@ -2076,9 +3075,9 @@ void PlotCoverage(struct site source, double altitude) fprintf(stdout,"\n50%c to 75%c ",37,37); fflush(stdout); - z=(int)(18.75*ReduceAngle(max_west-min_west)); + z=(int)(th*ReduceAngle(max_west-min_west)); - for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) + for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y)) { if (lon>=360.0) lon-=360.0; @@ -2107,9 +3106,9 @@ void PlotCoverage(struct site source, double altitude) fprintf(stdout,"\n75%c to 100%c ",37,37); fflush(stdout); - z=(int)(18.75*(max_north-min_north)); + z=(int)(th*(double)(max_north-min_north)); - for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel) + for (lat=(double)min_north, x=0, y=0; lat<(double)max_north; y++, lat=(double)min_north+(dpp*(double)y)) { edge.lat=lat; edge.lon=max_west; @@ -2151,7 +3150,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 @@ -2159,15 +3158,18 @@ void PlotLRMap(struct site source, double altitude) topographic map based on a receiver located at the specified altitude (in feet AGL). Results are stored in memory, and written out in the form - of a topographic map when the WritePPMLR() function - is later invoked. */ + of a topographic map when the WritePPMLR() or + WritePPMSS() functions are later invoked. */ - int z, count; + int y, z, count; struct site edge; - float lat, lon, one_pixel; - unsigned char symbol[4], x; + double lat, lon, minwest, maxnorth, th; + unsigned char x, symbol[4]; + static unsigned char mask_value=1; + FILE *fd=NULL; - one_pixel=1.0/1200.0; + minwest=dpp+(double)min_west; + maxnorth=(double)max_north-dpp; symbol[0]='.'; symbol[1]='o'; @@ -2176,16 +3178,44 @@ 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); - fflush(stdout); + fprintf(stdout,"\nComputing Longley-Rice "); - /* 18.75=1200 pixels/degree divided by 64 loops - per progress indicator symbol (.oOo) printed. */ + if (LR.erp==0.0) + fprintf(stdout,"path loss"); + else + { + if (dbm) + fprintf(stdout,"signal power level"); + else + fprintf(stdout,"field strength"); + } + + fprintf(stdout," contours of \"%s\"\nout to a radius of %.2f %s with an RX antenna at %.2f %s AGL",source.name,metric?max_range*KM_PER_MILE:max_range,metric?"kilometers":"miles",metric?altitude*METERS_PER_FOOT:altitude,metric?"meters":"feet"); - z=(int)(18.75*ReduceAngle(max_west-min_west)); + if (clutter>0.0) + fprintf(stdout,"\nand %.2f %s of ground clutter",metric?clutter*METERS_PER_FOOT:clutter,metric?"meters":"feet"); - for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) + fprintf(stdout,"...\n\n 0%c to 25%c ",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); + } + + /* th=pixels/degree divided by 64 loops per + progress indicator symbol (.oOo) printed. */ + + th=ppd/64.0; + + z=(int)(th*ReduceAngle(max_west-min_west)); + + for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y)) { if (lon>=360.0) lon-=360.0; @@ -2194,7 +3224,7 @@ void PlotLRMap(struct site source, double altitude) edge.lon=lon; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,mask_value,fd); count++; if (count==z) @@ -2214,15 +3244,15 @@ void PlotLRMap(struct site source, double altitude) fprintf(stdout,"\n25%c to 50%c ",37,37); fflush(stdout); - z=(int)(18.75*(max_north-min_north)); + z=(int)(th*(double)(max_north-min_north)); - for (lat=max_north, x=0; lat>=min_north; lat-=one_pixel) + for (lat=maxnorth, x=0, y=0; lat>=(double)min_north; y++, lat=maxnorth-(dpp*(double)y)) { edge.lat=lat; edge.lon=min_west; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,mask_value,fd); count++; if (count==z) @@ -2242,9 +3272,9 @@ void PlotLRMap(struct site source, double altitude) fprintf(stdout,"\n50%c to 75%c ",37,37); fflush(stdout); - z=(int)(18.75*ReduceAngle(max_west-min_west)); + z=(int)(th*ReduceAngle(max_west-min_west)); - for (lon=min_west, x=0; (LonDiff(lon,max_west)<=0.0); lon+=one_pixel) + for (lon=minwest, x=0, y=0; (LonDiff(lon,(double)max_west)<=0.0); y++, lon=minwest+(dpp*(double)y)) { if (lon>=360.0) lon-=360.0; @@ -2253,7 +3283,7 @@ void PlotLRMap(struct site source, double altitude) edge.lon=lon; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,mask_value,fd); count++; if (count==z) @@ -2273,15 +3303,15 @@ void PlotLRMap(struct site source, double altitude) fprintf(stdout,"\n75%c to 100%c ",37,37); fflush(stdout); - z=(int)(18.75*(max_north-min_north)); + z=(int)(th*(double)(max_north-min_north)); - for (lat=min_north, x=0; lat<=max_north; lat+=one_pixel) + for (lat=(double)min_north, x=0, y=0; lat<(double)max_north; y++, lat=(double)min_north+(dpp*(double)y)) { edge.lat=lat; edge.lon=max_west; edge.alt=altitude; - PlotLRPath(source,edge); + PlotLRPath(source,edge,mask_value,fd); count++; if (count==z) @@ -2297,11 +3327,501 @@ void PlotLRMap(struct site source, double altitude) } } + if (fd!=NULL) + fclose(fd); + fprintf(stdout,"\nDone!\n"); fflush(stdout); + + if (mask_value<30) + mask_value++; +} + +void LoadSignalColors(struct site xmtr) +{ + int x, y, ok, val[4]; + char filename[255], string[80], *pointer=NULL, *s=NULL; + FILE *fd=NULL; + + for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++) + filename[x]=xmtr.filename[x]; + + filename[x]='.'; + filename[x+1]='s'; + filename[x+2]='c'; + filename[x+3]='f'; + filename[x+4]=0; + + /* Default values */ + + region.level[0]=128; + region.color[0][0]=255; + region.color[0][1]=0; + region.color[0][2]=0; + + region.level[1]=118; + region.color[1][0]=255; + region.color[1][1]=165; + region.color[1][2]=0; + + region.level[2]=108; + region.color[2][0]=255; + region.color[2][1]=206; + region.color[2][2]=0; + + region.level[3]=98; + region.color[3][0]=255; + region.color[3][1]=255; + region.color[3][2]=0; + + region.level[4]=88; + region.color[4][0]=184; + region.color[4][1]=255; + region.color[4][2]=0; + + region.level[5]=78; + region.color[5][0]=0; + region.color[5][1]=255; + region.color[5][2]=0; + + region.level[6]=68; + region.color[6][0]=0; + region.color[6][1]=208; + region.color[6][2]=0; + + region.level[7]=58; + region.color[7][0]=0; + region.color[7][1]=196; + region.color[7][2]=196; + + region.level[8]=48; + region.color[8][0]=0; + region.color[8][1]=148; + region.color[8][2]=255; + + region.level[9]=38; + region.color[9][0]=80; + region.color[9][1]=80; + region.color[9][2]=255; + + region.level[10]=28; + region.color[10][0]=0; + region.color[10][1]=38; + region.color[10][2]=255; + + region.level[11]=18; + region.color[11][0]=142; + region.color[11][1]=63; + region.color[11][2]=255; + + region.level[12]=8; + region.color[12][0]=140; + region.color[12][1]=0; + region.color[12][2]=128; + + region.levels=13; + + fd=fopen("splat.scf","r"); + + if (fd==NULL) + fd=fopen(filename,"r"); + + if (fd==NULL) + { + fd=fopen(filename,"w"); + + fprintf(fd,"; SPLAT! Auto-generated Signal Color Definition (\"%s\") File\n",filename); + fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n"); + fprintf(fd,"; dBuV/m: red, green, blue\n;\n"); + fprintf(fd,"; ...where \"dBuV/m\" is the signal strength (in dBuV/m) and\n"); + fprintf(fd,"; \"red\", \"green\", and \"blue\" are the corresponding RGB color\n"); + fprintf(fd,"; definitions ranging from 0 to 255 for the region specified.\n"); + fprintf(fd,";\n; The following parameters may be edited and/or expanded\n"); + fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n"); + fprintf(fd,"; may be defined in this file.\n;\n;\n"); + + for (x=0; x255) + val[y]=255; + + if (val[y]<0) + val[y]=0; + } + + region.level[x]=val[0]; + region.color[x][0]=val[1]; + region.color[x][1]=val[2]; + region.color[x][2]=val[3]; + x++; + } + + s=fgets(string,80,fd); + } + + fclose(fd); + region.levels=x; + } +} + +void LoadLossColors(struct site xmtr) +{ + int x, y, ok, val[4]; + char filename[255], string[80], *pointer=NULL, *s=NULL; + FILE *fd=NULL; + + for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++) + filename[x]=xmtr.filename[x]; + + filename[x]='.'; + filename[x+1]='l'; + filename[x+2]='c'; + filename[x+3]='f'; + filename[x+4]=0; + + /* Default values */ + + region.level[0]=80; + region.color[0][0]=255; + region.color[0][1]=0; + region.color[0][2]=0; + + region.level[1]=90; + region.color[1][0]=255; + region.color[1][1]=128; + region.color[1][2]=0; + + region.level[2]=100; + region.color[2][0]=255; + region.color[2][1]=165; + region.color[2][2]=0; + + region.level[3]=110; + region.color[3][0]=255; + region.color[3][1]=206; + region.color[3][2]=0; + + region.level[4]=120; + region.color[4][0]=255; + region.color[4][1]=255; + region.color[4][2]=0; + + region.level[5]=130; + region.color[5][0]=184; + region.color[5][1]=255; + region.color[5][2]=0; + + region.level[6]=140; + region.color[6][0]=0; + region.color[6][1]=255; + region.color[6][2]=0; + + region.level[7]=150; + region.color[7][0]=0; + region.color[7][1]=208; + region.color[7][2]=0; + + region.level[8]=160; + region.color[8][0]=0; + region.color[8][1]=196; + region.color[8][2]=196; + + region.level[9]=170; + region.color[9][0]=0; + region.color[9][1]=148; + region.color[9][2]=255; + + region.level[10]=180; + region.color[10][0]=80; + region.color[10][1]=80; + region.color[10][2]=255; + + region.level[11]=190; + region.color[11][0]=0; + region.color[11][1]=38; + region.color[11][2]=255; + + region.level[12]=200; + region.color[12][0]=142; + region.color[12][1]=63; + region.color[12][2]=255; + + region.level[13]=210; + region.color[13][0]=196; + region.color[13][1]=54; + region.color[13][2]=255; + + region.level[14]=220; + region.color[14][0]=255; + region.color[14][1]=0; + region.color[14][2]=255; + + region.level[15]=230; + region.color[15][0]=255; + region.color[15][1]=194; + region.color[15][2]=204; + + region.levels=16; + + fd=fopen("splat.lcf","r"); + + if (fd==NULL) + fd=fopen(filename,"r"); + + if (fd==NULL) + { + fd=fopen(filename,"w"); + + fprintf(fd,"; SPLAT! Auto-generated Path-Loss Color Definition (\"%s\") File\n",filename); + fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n"); + fprintf(fd,"; dB: red, green, blue\n;\n"); + fprintf(fd,"; ...where \"dB\" is the path loss (in dB) and\n"); + fprintf(fd,"; \"red\", \"green\", and \"blue\" are the corresponding RGB color\n"); + fprintf(fd,"; definitions ranging from 0 to 255 for the region specified.\n"); + fprintf(fd,";\n; The following parameters may be edited and/or expanded\n"); + fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n"); + fprintf(fd,"; may be defined in this file.\n;\n;\n"); + + for (x=0; x255) + val[y]=255; + + if (val[y]<0) + val[y]=0; + } + + region.level[x]=val[0]; + region.color[x][0]=val[1]; + region.color[x][1]=val[2]; + region.color[x][2]=val[3]; + x++; + } + + s=fgets(string,80,fd); + } + + fclose(fd); + region.levels=x; + } +} + +void LoadDBMColors(struct site xmtr) +{ + int x, y, ok, val[4]; + char filename[255], string[80], *pointer=NULL, *s=NULL; + FILE *fd=NULL; + + for (x=0; xmtr.filename[x]!='.' && xmtr.filename[x]!=0 && x<250; x++) + filename[x]=xmtr.filename[x]; + + filename[x]='.'; + filename[x+1]='d'; + filename[x+2]='c'; + filename[x+3]='f'; + filename[x+4]=0; + + /* Default values */ + + region.level[0]=0; + region.color[0][0]=255; + region.color[0][1]=0; + region.color[0][2]=0; + + region.level[1]=-10; + region.color[1][0]=255; + region.color[1][1]=128; + region.color[1][2]=0; + + region.level[2]=-20; + region.color[2][0]=255; + region.color[2][1]=165; + region.color[2][2]=0; + + region.level[3]=-30; + region.color[3][0]=255; + region.color[3][1]=206; + region.color[3][2]=0; + + region.level[4]=-40; + region.color[4][0]=255; + region.color[4][1]=255; + region.color[4][2]=0; + + region.level[5]=-50; + region.color[5][0]=184; + region.color[5][1]=255; + region.color[5][2]=0; + + region.level[6]=-60; + region.color[6][0]=0; + region.color[6][1]=255; + region.color[6][2]=0; + + region.level[7]=-70; + region.color[7][0]=0; + region.color[7][1]=208; + region.color[7][2]=0; + + region.level[8]=-80; + region.color[8][0]=0; + region.color[8][1]=196; + region.color[8][2]=196; + + region.level[9]=-90; + region.color[9][0]=0; + region.color[9][1]=148; + region.color[9][2]=255; + + region.level[10]=-100; + region.color[10][0]=80; + region.color[10][1]=80; + region.color[10][2]=255; + + region.level[11]=-110; + region.color[11][0]=0; + region.color[11][1]=38; + region.color[11][2]=255; + + region.level[12]=-120; + region.color[12][0]=142; + region.color[12][1]=63; + region.color[12][2]=255; + + region.level[13]=-130; + region.color[13][0]=196; + region.color[13][1]=54; + region.color[13][2]=255; + + region.level[14]=-140; + region.color[14][0]=255; + region.color[14][1]=0; + region.color[14][2]=255; + + region.level[15]=-150; + region.color[15][0]=255; + region.color[15][1]=194; + region.color[15][2]=204; + + region.levels=16; + + fd=fopen("splat.dcf","r"); + + if (fd==NULL) + fd=fopen(filename,"r"); + + if (fd==NULL) + { + fd=fopen(filename,"w"); + + fprintf(fd,"; SPLAT! Auto-generated DBM Signal Level Color Definition (\"%s\") File\n",filename); + fprintf(fd,";\n; Format for the parameters held in this file is as follows:\n;\n"); + fprintf(fd,"; dBm: red, green, blue\n;\n"); + fprintf(fd,"; ...where \"dBm\" is the received signal power level between +40 dBm\n"); + fprintf(fd,"; and -200 dBm, and \"red\", \"green\", and \"blue\" are the corresponding\n"); + fprintf(fd,"; RGB color definitions ranging from 0 to 255 for the region specified.\n"); + fprintf(fd,";\n; The following parameters may be edited and/or expanded\n"); + fprintf(fd,"; for future runs of SPLAT! A total of 32 contour regions\n"); + fprintf(fd,"; may be defined in this file.\n;\n;\n"); + + for (x=0; x+40) + val[0]=+40; + + region.level[x]=val[0]; + + for (y=1; y<4; y++) + { + if (val[y]>255) + val[y]=255; + + if (val[y]<0) + val[y]=0; + } + + region.color[x][0]=val[1]; + region.color[x][1]=val[2]; + region.color[x][2]=val[3]; + x++; + } + + s=fgets(string,80,fd); + } + + fclose(fd); + region.levels=x; + } } -void WritePPM(char *filename) +void WritePPM(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites) { /* This function generates a topographic map in Portable Pix Map (PPM) format based on logarithmically scaled topology data, @@ -2310,63 +3830,164 @@ void WritePPM(char *filename) from its representation in dem[][] so that north points up and east points right in the image generated. */ - char mapfile[255]; + char mapfile[255], geofile[255], kmlfile[255]; unsigned char found, mask; - unsigned width, height, output; - int indx, x, y, x0=0, y0=0, minlat, minlon; - float lat, lon, one_pixel, conversion, one_over_gamma; + unsigned width, height, terrain; + int indx, x, y, x0=0, y0=0; + double lat, lon, conversion, one_over_gamma, + north, south, east, west, minwest; 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=(unsigned)(1200*ReduceAngle(max_west-min_west)); - height=(unsigned)(1200*ReduceAngle(max_north-min_north)); + width=(unsigned)(ippd*ReduceAngle(max_west-min_west)); + height=(unsigned)(ippd*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]; + strncpy(filename, xmtr[0].filename,254); + filename[strlen(filename)-4]=0; /* Remove .qth */ + } + + y=strlen(filename); + + if (y>4) + { + if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.') + y-=4; + } + + for (x=0; x360.0) + minwest-=360.0; + + north=(double)max_north-dpp; + south=(double)min_north; + east=(minwest<180.0?-minwest:360.0-min_west); + west=(double)(max_west<180?-max_west:360-max_west); + + if (kml==0 && 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%.3f\t\t%.3f\n",west,north); + fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south); + fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height); + fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version); + + fclose(fd); + } + + if (kml && geo==0) + { + fd=fopen(kmlfile,"wb"); + + fprintf(fd,"\n"); + fprintf(fd,"\n"); + fprintf(fd," \n"); + fprintf(fd," %s\n",splat_name); + fprintf(fd," Line-of-Sight Overlay\n"); + fprintf(fd," \n"); + fprintf(fd," %s Line-of-Sight Overlay\n",splat_name); + fprintf(fd," SPLAT! Coverage\n"); + fprintf(fd," \n"); + fprintf(fd," %s\n",mapfile); + fprintf(fd," \n"); + fprintf(fd," 128\n"); + fprintf(fd," \n"); + fprintf(fd," %.5f\n",north); + fprintf(fd," %.5f\n",south); + fprintf(fd," %.5f\n",east); + fprintf(fd," %.5f\n",west); + fprintf(fd," 0.0\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + + for (x=0; x\n"); + fprintf(fd," %s\n",xmtr[x].name); + fprintf(fd," 1\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," %f,%f,%f\n",(xmtr[x].lon<180.0?-xmtr[x].lon:360.0-xmtr[x].lon), xmtr[x].lat, xmtr[x].alt); + fprintf(fd," \n"); + fprintf(fd," \n"); + } + - mapfile[x]='.'; - mapfile[x+1]='p'; - mapfile[x+2]='p'; - mapfile[x+3]='m'; - mapfile[x+4]=0; + + fprintf(fd," \n"); + fprintf(fd,"\n"); + + fclose(fd); } fd=fopen(mapfile,"wb"); fprintf(fd,"P6\n%u %u\n255\n",width,height); - fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height); fflush(stdout); - for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel) + for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y)) { - minlat=(int)floor(lat); - - for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel) + for (x=0, lon=max_west; x<(int)width; x++, lon=(double)max_west-(dpp*(double)x)) { if (lon<0.0) lon+=360.0; - minlon=(int)floor(lon); + for (indx=0, found=0; indx=0 && x0<=mpi && y0>=0 && y0<=mpi) found=1; else indx++; + } if (found) { - x0=(int)(1199.0*(lat-floor(lat))); - y0=(int)(1199.0*(lon-floor(lon))); - mask=dem[indx].mask[x0][y0]; if (mask&2) @@ -2455,14 +4076,19 @@ void WritePPM(char *filename) break; default: - /* Water: Medium Blue */ - if (dem[indx].data[x0][y0]==0) - fprintf(fd,"%c%c%c",0,0,170); + if (ngs) /* No terrain */ + fprintf(fd,"%c%c%c",255,255,255); 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); + /* Sea-level: Medium Blue */ + if (dem[indx].data[x0][y0]==0) + fprintf(fd,"%c%c%c",0,0,170); + else + { + /* Elevation: Greyscale */ + terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); + fprintf(fd,"%c%c%c",terrain,terrain,terrain); + } } } } @@ -2482,7 +4108,7 @@ void WritePPM(char *filename) fflush(stdout); } -void WritePPMLR(char *filename) +void WritePPMLR(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites) { /* This function generates a topographic map in Portable Pix Map (PPM) format based on the content of flags held in the mask[][] @@ -2490,77 +4116,211 @@ void WritePPMLR(char *filename) 90 degrees from its representation in dem[][] so that north points up and east points right in the image generated. */ - char mapfile[255]; - unsigned width, height, output; + char mapfile[255], geofile[255], kmlfile[255]; + unsigned width, height, red, green, blue, terrain=0; unsigned char found, mask, cityorcounty; - int indx, x, y, t, t2, x0, y0, minlat, minlon, loss; - float lat, lon, one_pixel, conversion, one_over_gamma; + int indx, x, y, z, colorwidth, x0, y0, loss, level, + hundreds, tens, units, match; + double lat, lon, conversion, one_over_gamma, + north, south, east, west, minwest; 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=(unsigned)(1200*ReduceAngle(max_west-min_west)); - height=(unsigned)(1200*ReduceAngle(max_north-min_north)); + width=(unsigned)(ippd*ReduceAngle(max_west-min_west)); + height=(unsigned)(ippd*ReduceAngle(max_north-min_north)); + + LoadLossColors(xmtr[0]); if (filename[0]==0) - strncpy(mapfile, "map.ppm\0",8); + { + strncpy(filename, xmtr[0].filename,254); + filename[strlen(filename)-4]=0; /* Remove .qth */ + } + + y=strlen(filename); + + if (y>4) + { + if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.') + y-=4; + } + + for (x=0; x360.0) + minwest-=360.0; + + north=(double)max_north-dpp; + + if (kml || geo) + south=(double)min_north; /* No bottom legend */ else + south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */ + + east=(minwest<180.0?-minwest:360.0-min_west); + west=(double)(max_west<180?-max_west:360-max_west); + + if (kml==0 && 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%.3f\t\t%.3f\n",west,north); + + fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south); + fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height); + + fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version); + + fclose(fd); + } + + if (kml && geo==0) { - for (x=0; filename[x]!='.' && filename[x]!=0 && x<250; x++) - mapfile[x]=filename[x]; + fd=fopen(kmlfile,"wb"); + + fprintf(fd,"\n"); + fprintf(fd,"\n"); + fprintf(fd,"\n",splat_name,splat_version); + fprintf(fd," \n"); + fprintf(fd," %s\n",splat_name); + fprintf(fd," %s Transmitter Path Loss Overlay\n",xmtr[0].name); + fprintf(fd," \n"); + fprintf(fd," SPLAT! Path Loss Overlay\n"); + fprintf(fd," SPLAT! Coverage\n"); + fprintf(fd," \n"); + fprintf(fd," %s\n",mapfile); + fprintf(fd," \n"); + fprintf(fd," 128\n"); + fprintf(fd," \n"); + fprintf(fd," %.5f\n",north); + fprintf(fd," %.5f\n",south); + fprintf(fd," %.5f\n",east); + fprintf(fd," %.5f\n",west); + fprintf(fd," 0.0\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + + for (x=0; x\n"); + fprintf(fd," %s\n",xmtr[x].name); + fprintf(fd," 1\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," %f,%f,%f\n",(xmtr[x].lon<180.0?-xmtr[x].lon:360.0-xmtr[x].lon), xmtr[x].lat, xmtr[x].alt); + fprintf(fd," \n"); + fprintf(fd," \n"); + } + + fprintf(fd," \n"); + fprintf(fd,"\n"); - mapfile[x]='.'; - mapfile[x+1]='p'; - mapfile[x+2]='p'; - mapfile[x+3]='m'; - mapfile[x+4]=0; + fclose(fd); } fd=fopen(mapfile,"wb"); - fprintf(fd,"P6\n%u %u\n255\n",width,height+30); - - fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,height+30); + fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30)); + fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30)); fflush(stdout); - for (y=0, lat=((double)max_north)-one_pixel; y<(int)height; y++, lat-=one_pixel) + for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y)) { - minlat=(int)floor(lat); - - for (x=0, lon=((double)max_west)-one_pixel; x<(int)width; x++, lon-=one_pixel) + for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x)) { if (lon<0.0) lon+=360.0; - minlon=(int)floor(lon); + for (indx=0, found=0; indx=0 && x0<=mpi && y0>=0 && y0<=mpi) found=1; else indx++; + } + if (found) { - x0=(int)(1199.0*(lat-floor(lat))); - y0=(int)(1199.0*(lon-floor(lon))); - mask=dem[indx].mask[x0][y0]; - loss=70+(10*(int)((mask&248)>>3)); + loss=(dem[indx].signal[x0][y0]); cityorcounty=0; + match=255; + + red=0; + green=0; + blue=0; + + if (loss<=region.level[0]) + match=0; + else + { + for (z=1; (z=region.level[z-1] && loss=180 && green<=75 && blue<=75 && loss!=0) + fprintf(fd,"%c%c%c",255^red,255^green,255^blue); + else + fprintf(fd,"%c%c%c",255,0,0); - cityorcounty=1; + cityorcounty=1; } else if (mask&4) @@ -2574,97 +4334,398 @@ void WritePPMLR(char *filename) if (cityorcounty==0) { - if (loss>maxdB) - - { /* Display land or sea elevation */ - - if (dem[indx].data[x0][y0]==0) - fprintf(fd,"%c%c%c",0,0,170); + if (loss==0 || (contour_threshold!=0 && loss>abs(contour_threshold))) + { + if (ngs) /* No terrain */ + fprintf(fd,"%c%c%c",255,255,255); 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); + /* Display land or sea elevation */ + + if (dem[indx].data[x0][y0]==0) + fprintf(fd,"%c%c%c",0,0,170); + else + { + terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); + fprintf(fd,"%c%c%c",terrain,terrain,terrain); + } } } - else switch (loss) + else { - /* Plot signal loss in color */ + /* Plot path loss in color */ - case 80: - fprintf(fd,"%c%c%c",255,0,0); - break; + if (red!=0 || green!=0 || blue!=0) + fprintf(fd,"%c%c%c",red,green,blue); - case 90: - fprintf(fd,"%c%c%c",255,128,0); - break; + else /* terrain / sea-level */ + { + if (dem[indx].data[x0][y0]==0) + fprintf(fd,"%c%c%c",0,0,170); + else + { + /* Elevation: Greyscale */ + terrain=(unsigned)(0.5+pow((double)(dem[indx].data[x0][y0]-min_elevation),one_over_gamma)*conversion); + fprintf(fd,"%c%c%c",terrain,terrain,terrain); + } + } + } + } + } - case 100: - fprintf(fd,"%c%c%c",255,165,0); - break; + else + { + /* We should never get here, but if */ + /* we do, display the region as black */ - case 110: - fprintf(fd,"%c%c%c",255,206,0); - break; + fprintf(fd,"%c%c%c",0,0,0); + } + } + } - case 120: - fprintf(fd,"%c%c%c",255,255,0); - break; + if (kml==0 && geo==0) + { + /* Display legend along bottom of image + * if not generating .kml or .geo output. + */ - case 130: - fprintf(fd,"%c%c%c",184,255,0); - break; + colorwidth=(int)rint((float)width/(float)region.levels); - case 140: - fprintf(fd,"%c%c%c",0,255,0); - break; + for (y0=0; y0<30; y0++) + { + for (x0=0; x0<(int)width; x0++) + { + indx=x0/colorwidth; + x=x0%colorwidth; + level=region.level[indx]; - case 150: - fprintf(fd,"%c%c%c",0,208,0); - break; + hundreds=level/100; - case 160: - fprintf(fd,"%c%c%c",0,196,196); - break; + if (hundreds>0) + level-=(hundreds*100); - case 170: - fprintf(fd,"%c%c%c",0,148,255); - break; + tens=level/10; - case 180: - fprintf(fd,"%c%c%c",80,80,255); - break; + if (tens>0) + level-=(tens*10); - case 190: - fprintf(fd,"%c%c%c",0,38,255); - break; + units=level; - case 200: - fprintf(fd,"%c%c%c",142,63,255); - break; + if (y0>=8 && y0<=23) + { + if (hundreds>0) + { + if (x>=11 && x<=18) + if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-11))) + indx=255; + } - case 210: - fprintf(fd,"%c%c%c",196,54,255); - break; + if (tens>0 || hundreds>0) + { + if (x>=19 && x<=26) + if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-19))) + indx=255; + } + + if (x>=27 && x<=34) + if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-27))) + indx=255; - case 220: - fprintf(fd,"%c%c%c",255,0,255); - break; + if (x>=42 && x<=49) + if (fontdata[16*('d')+(y0-8)]&(128>>(x-42))) + indx=255; - case 230: - fprintf(fd,"%c%c%c",255,194,204); - break; + if (x>=50 && x<=57) + if (fontdata[16*('B')+(y0-8)]&(128>>(x-50))) + indx=255; + } - default: + if (indx>region.levels) + fprintf(fd,"%c%c%c",0,0,0); + else + { + red=region.color[indx][0]; + green=region.color[indx][1]; + blue=region.color[indx][2]; - if (dem[indx].data[x0][y0]==0) - fprintf(fd,"%c%c%c",0,0,170); + fprintf(fd,"%c%c%c",red,green,blue); + } + } + } + } + + fclose(fd); + fprintf(stdout,"Done!\n"); + fflush(stdout); +} + +void WritePPMSS(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites) +{ + /* This function generates a topographic map in Portable Pix Map + (PPM) format based on the signal strength values held in the + signal[][] array. The image created is rotated counter-clockwise + 90 degrees from its representation in dem[][] so that north + points up and east points right in the image generated. */ + + char mapfile[255], geofile[255], kmlfile[255]; + unsigned width, height, terrain, red, green, blue; + unsigned char found, mask, cityorcounty; + int indx, x, y, z=1, x0, y0, signal, level, hundreds, + tens, units, match, colorwidth; + double conversion, one_over_gamma, lat, lon, + north, south, east, west, minwest; + FILE *fd; + + one_over_gamma=1.0/GAMMA; + conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma); + + width=(unsigned)(ippd*ReduceAngle(max_west-min_west)); + height=(unsigned)(ippd*ReduceAngle(max_north-min_north)); + + LoadSignalColors(xmtr[0]); + + if (filename[0]==0) + { + strncpy(filename, xmtr[0].filename,254); + filename[strlen(filename)-4]=0; /* Remove .qth */ + } + + y=strlen(filename); + + if (y>4) + { + if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.') + y-=4; + } + + for (x=0; x360.0) + minwest-=360.0; + + north=(double)max_north-dpp; + + if (kml || geo) + south=(double)min_north; /* No bottom legend */ + else + south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */ + + east=(minwest<180.0?-minwest:360.0-min_west); + west=(double)(max_west<180?-max_west:360-max_west); + + if (geo && kml==0) + { + fd=fopen(geofile,"wb"); + + fprintf(fd,"FILENAME\t%s\n",mapfile); + fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n"); + fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north); + + fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south); + fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height); + + fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version); + + fclose(fd); + } + + if (kml && geo==0) + { + fd=fopen(kmlfile,"wb"); + + fprintf(fd,"\n"); + fprintf(fd,"\n"); + fprintf(fd,"\n",splat_name,splat_version); + fprintf(fd," \n"); + fprintf(fd," %s\n",splat_name); + fprintf(fd," %s Transmitter Coverage Overlay\n",xmtr[0].name); + fprintf(fd," \n"); + fprintf(fd," SPLAT! Signal Strength Overlay\n"); + fprintf(fd," SPLAT! Coverage\n"); + fprintf(fd," \n"); + fprintf(fd," %s\n",mapfile); + fprintf(fd," \n"); + fprintf(fd," 128\n"); + fprintf(fd," \n"); + fprintf(fd," %.5f\n",north); + fprintf(fd," %.5f\n",south); + fprintf(fd," %.5f\n",east); + fprintf(fd," %.5f\n",west); + fprintf(fd," 0.0\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + + for (x=0; x\n"); + fprintf(fd," %s\n",xmtr[x].name); + fprintf(fd," 1\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," %f,%f,%f\n",(xmtr[x].lon<180.0?-xmtr[x].lon:360.0-xmtr[x].lon), xmtr[x].lat, xmtr[x].alt); + fprintf(fd," \n"); + fprintf(fd," \n"); + } + + fprintf(fd," \n"); + fprintf(fd,"\n"); + + fclose(fd); + } + + fd=fopen(mapfile,"wb"); + + fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30)); + fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30)); + fflush(stdout); + + for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y)) + { + for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x)) + { + if (lon<0.0) + lon+=360.0; + + for (indx=0, found=0; indx=0 && x0<=mpi && y0>=0 && y0<=mpi) + found=1; + else + indx++; + } + + if (found) + { + mask=dem[indx].mask[x0][y0]; + signal=(dem[indx].signal[x0][y0])-100; + cityorcounty=0; + + match=255; + + red=0; + green=0; + blue=0; + + if (signal>=region.level[0]) + match=0; + else + { + for (z=1; (z=region.level[z]) + match=z; + } + } + + if (match=180 && green<=75 && blue<=75) + fprintf(fd,"%c%c%c",255^red,255^green,255^blue); + else + fprintf(fd,"%c%c%c",255,0,0); + + cityorcounty=1; + } + + else if (mask&4) + { + /* County Boundaries: Black */ + + fprintf(fd,"%c%c%c",0,0,0); + + cityorcounty=1; + } + + if (cityorcounty==0) + { + if (contour_threshold!=0 && signal=10 && y0<=18) - { - if (t2>9) - { - if (x>=11 && x<=17) - if (smallfont[t2/10][y0-10][x-11]) - t=255; - } - - if (x>=19 && x<=25) - if (smallfont[t2%10][y0-10][x-19]) - t=255; - - if (x>=27 && x<=33) - if (smallfont[0][y0-10][x-27]) - t=255; - } - - switch (t) - { - case 0: - fprintf(fd,"%c%c%c",255,0,0); - break; - - case 1: - fprintf(fd,"%c%c%c",255,128,0); - break; + indx=x0/colorwidth; + x=x0%colorwidth; + level=region.level[indx]; - case 2: - fprintf(fd,"%c%c%c",255,165,0); - break; + hundreds=level/100; - case 3: - fprintf(fd,"%c%c%c",255,206,0); - break; + if (hundreds>0) + level-=(hundreds*100); - case 4: - fprintf(fd,"%c%c%c",255,255,0); - break; + tens=level/10; - case 5: - fprintf(fd,"%c%c%c",184,255,0); - break; + if (tens>0) + level-=(tens*10); - case 6: - fprintf(fd,"%c%c%c",0,255,0); - break; + units=level; - case 7: - fprintf(fd,"%c%c%c",0,208,0); - break; + if (y0>=8 && y0<=23) + { + if (hundreds>0) + { + if (x>=5 && x<=12) + if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-5))) + indx=255; + } - case 8: - fprintf(fd,"%c%c%c",0,196,196); - break; + if (tens>0 || hundreds>0) + { + if (x>=13 && x<=20) + if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-13))) + indx=255; + } + + if (x>=21 && x<=28) + if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-21))) + indx=255; - case 9: - fprintf(fd,"%c%c%c",0,148,255); - break; + if (x>=36 && x<=43) + if (fontdata[16*('d')+(y0-8)]&(128>>(x-36))) + indx=255; - case 10: - fprintf(fd,"%c%c%c",80,80,255); - break; + if (x>=44 && x<=51) + if (fontdata[16*('B')+(y0-8)]&(128>>(x-44))) + indx=255; - case 11: - fprintf(fd,"%c%c%c",0,38,255); - break; + if (x>=52 && x<=59) + if (fontdata[16*(230)+(y0-8)]&(128>>(x-52))) + indx=255; - case 12: - fprintf(fd,"%c%c%c",142,63,255); - break; + if (x>=60 && x<=67) + if (fontdata[16*('V')+(y0-8)]&(128>>(x-60))) + indx=255; - case 13: - fprintf(fd,"%c%c%c",196,54,255); - break; + if (x>=68 && x<=75) + if (fontdata[16*('/')+(y0-8)]&(128>>(x-68))) + indx=255; - case 14: - fprintf(fd,"%c%c%c",255,0,255); - break; + if (x>=76 && x<=83) + if (fontdata[16*('m')+(y0-8)]&(128>>(x-76))) + indx=255; + } - case 255: - /* Black */ + if (indx>region.levels) fprintf(fd,"%c%c%c",0,0,0); - break; + else + { + red=region.color[indx][0]; + green=region.color[indx][1]; + blue=region.color[indx][2]; - default: - fprintf(fd,"%c%c%c",255,194,204); + fprintf(fd,"%c%c%c",red,green,blue); } } } @@ -2790,666 +4833,2183 @@ void WritePPMLR(char *filename) fflush(stdout); } -void GraphTerrain(struct site source, struct site destination, char *name) +void WritePPMDBM(char *filename, unsigned char geo, unsigned char kml, unsigned char ngs, struct site *xmtr, unsigned char txsites) { - /* This function invokes gnuplot to generate an appropriate - output file indicating the terrain profile between the source - 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. */ + /* This function generates a topographic map in Portable Pix Map + (PPM) format based on the signal power level values held in the + signal[][] array. The image created is rotated counter-clockwise + 90 degrees from its representation in dem[][] so that north + points up and east points right in the image generated. */ - int x, y, z; - char filename[255], term[15], ext[15]; - FILE *fd=NULL; + char mapfile[255], geofile[255], kmlfile[255]; + unsigned width, height, terrain, red, green, blue; + unsigned char found, mask, cityorcounty; + int indx, x, y, z=1, x0, y0, dBm, level, hundreds, + tens, units, match, colorwidth; + double conversion, one_over_gamma, lat, lon, + north, south, east, west, minwest; + FILE *fd; - ReadPath(destination,source); + one_over_gamma=1.0/GAMMA; + conversion=255.0/pow((double)(max_elevation-min_elevation),one_over_gamma); - fd=fopen("profile.gp","wb"); + width=(unsigned)(ippd*ReduceAngle(max_west-min_west)); + height=(unsigned)(ippd*ReduceAngle(max_north-min_north)); - for (x=0; x4) { - /* Default filename and output file type */ + if (filename[y-1]=='m' && filename[y-2]=='p' && filename[y-3]=='p' && filename[y-4]=='.') + y-=4; + } - strncpy(filename,"profile\0",8); - strncpy(term,"gif\0",4); - strncpy(ext,"gif\0",4); + for (x=0; x360.0) + minwest-=360.0; + + north=(double)max_north-dpp; + + if (kml || geo) + south=(double)min_north; /* No bottom legend */ else + south=(double)min_north-(30.0/ppd); /* 30 pixels for bottom legend */ + + east=(minwest<180.0?-minwest:360.0-min_west); + west=(double)(max_west<180?-max_west:360-max_west); + + if (geo && kml==0) { - /* Grab extension and terminal type from "name" */ + fd=fopen(geofile,"wb"); - for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++) - filename[x]=name[x]; + fprintf(fd,"FILENAME\t%s\n",mapfile); + fprintf(fd,"#\t\tX\tY\tLong\t\tLat\n"); + fprintf(fd,"TIEPOINT\t0\t0\t%.3f\t\t%.3f\n",west,north); - if (name[x]=='.') - { - for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++) - { - term[y]=tolower(name[x]); - ext[y]=term[y]; - } + fprintf(fd,"TIEPOINT\t%u\t%u\t%.3f\t\t%.3f\n",width-1,height-1,east,south); + fprintf(fd,"IMAGESIZE\t%u\t%u\n",width,height); - ext[y]=0; - term[y]=0; - filename[z]=0; - } + fprintf(fd,"#\n# Auto Generated by %s v%s\n#\n",splat_name,splat_version); - else - { /* No extension -- Default is gif */ + fclose(fd); + } - filename[x]=0; - strncpy(term,"gif\0",4); - strncpy(ext,"gif\0",4); + if (kml && geo==0) + { + fd=fopen(kmlfile,"wb"); + + fprintf(fd,"\n"); + fprintf(fd,"\n"); + fprintf(fd,"\n",splat_name,splat_version); + fprintf(fd," \n"); + fprintf(fd," %s\n",splat_name); + fprintf(fd," %s Transmitter Coverage Overlay\n",xmtr[0].name); + fprintf(fd," \n"); + fprintf(fd," SPLAT! Signal Power Level Overlay\n"); + fprintf(fd," SPLAT! Coverage\n"); + fprintf(fd," \n"); + fprintf(fd," %s\n",mapfile); + fprintf(fd," \n"); + fprintf(fd," 128\n"); + fprintf(fd," \n"); + fprintf(fd," %.5f\n",north); + fprintf(fd," %.5f\n",south); + fprintf(fd," %.5f\n",east); + fprintf(fd," %.5f\n",west); + fprintf(fd," 0.0\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + + for (x=0; x\n"); + fprintf(fd," %s\n",xmtr[x].name); + fprintf(fd," 1\n"); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd," 1\n"); + fprintf(fd," relativeToGround\n"); + fprintf(fd," %f,%f,%f\n",(xmtr[x].lon<180.0?-xmtr[x].lon:360.0-xmtr[x].lon), xmtr[x].lat, xmtr[x].alt); + fprintf(fd," \n"); + fprintf(fd," \n"); } - } - /* Either .ps or .postscript may be used - as an extension for postscript output. */ + fprintf(fd," \n"); + fprintf(fd,"\n"); - if (strncmp(term,"postscript",10)==0) - strncpy(ext,"ps\0",3); + fclose(fd); + } - else if (strncmp(ext,"ps",2)==0) - strncpy(term,"postscript\0",11); + fd=fopen(mapfile,"wb"); - fprintf(stdout,"Writing \"%s.%s\"...",filename,ext); + fprintf(fd,"P6\n%u %u\n255\n",width,(kml?height:height+30)); + fprintf(stdout,"\nWriting \"%s\" (%ux%u pixmap image)... ",mapfile,width,(kml?height:height+30)); fflush(stdout); - fd=fopen("splat.gp","w"); - fprintf(fd,"set grid\n"); - fprintf(fd,"set autoscale\n"); - fprintf(fd,"set term %s\n",term); - fprintf(fd,"set title \"SPLAT! Terrain Profile\"\n"); - fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name); - fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (feet)\"\n"); - fprintf(fd,"set output \"%s.%s\"\n",filename,ext); - fprintf(fd,"plot \"profile.gp\" title \"\" with lines\n"); - fclose(fd); - - x=system("gnuplot splat.gp"); - - if (x!=-1) + for (y=0, lat=north; y<(int)height; y++, lat=north-(dpp*(double)y)) { - unlink("splat.gp"); - unlink("profile.gp"); - fprintf(stdout," Done!\n"); - fflush(stdout); - } + for (x=0, lon=max_west; x<(int)width; x++, lon=max_west-(dpp*(double)x)) + { + if (lon<0.0) + lon+=360.0; - else - fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); -} + for (indx=0, found=0; indx=0 && x0<=mpi && y0>=0 && y0<=mpi) + found=1; + else + indx++; + } + + if (found) + { + mask=dem[indx].mask[x0][y0]; + dBm=(dem[indx].signal[x0][y0])-200; + cityorcounty=0; + + match=255; + + red=0; + green=0; + blue=0; + + if (dBm>=region.level[0]) + match=0; + else + { + for (z=1; (z=region.level[z]) + match=z; + } + } + + if (match=180 && green<=75 && blue<=75 && dBm!=0) + fprintf(fd,"%c%c%c",255^red,255^green,255^blue); + else + fprintf(fd,"%c%c%c",255,0,0); + + cityorcounty=1; + } + + else if (mask&4) + { + /* County Boundaries: Black */ + + fprintf(fd,"%c%c%c",0,0,0); + + cityorcounty=1; + } + + if (cityorcounty==0) + { + if (contour_threshold!=0 && dBm0) + level-=(hundreds*100); + + tens=level/10; + + if (tens>0) + level-=(tens*10); + + units=level; + + if (y0>=8 && y0<=23) + { + if (hundreds>0) + { + if (region.level[indx]<0) + { + if (x>=5 && x<=12) + if (fontdata[16*('-')+(y0-8)]&(128>>(x-5))) + indx=255; + } + + else + { + if (x>=5 && x<=12) + if (fontdata[16*('+')+(y0-8)]&(128>>(x-5))) + indx=255; + } + + if (x>=13 && x<=20) + if (fontdata[16*(hundreds+'0')+(y0-8)]&(128>>(x-13))) + indx=255; + } + + if (tens>0 || hundreds>0) + { + if (hundreds==0) + { + if (region.level[indx]<0) + { + if (x>=13 && x<=20) + if (fontdata[16*('-')+(y0-8)]&(128>>(x-13))) + indx=255; + } + + else + { + if (x>=13 && x<=20) + if (fontdata[16*('+')+(y0-8)]&(128>>(x-13))) + indx=255; + } + } + + if (x>=21 && x<=28) + if (fontdata[16*(tens+'0')+(y0-8)]&(128>>(x-21))) + indx=255; + } + + if (hundreds==0 && tens==0) + { + if (region.level[indx]<0) + { + if (x>=21 && x<=28) + if (fontdata[16*('-')+(y0-8)]&(128>>(x-21))) + indx=255; + } + + else + { + if (x>=21 && x<=28) + if (fontdata[16*('+')+(y0-8)]&(128>>(x-21))) + indx=255; + } + } + + if (x>=29 && x<=36) + if (fontdata[16*(units+'0')+(y0-8)]&(128>>(x-29))) + indx=255; + + if (x>=37 && x<=44) + if (fontdata[16*('d')+(y0-8)]&(128>>(x-37))) + indx=255; + + if (x>=45 && x<=52) + if (fontdata[16*('B')+(y0-8)]&(128>>(x-45))) + indx=255; + + if (x>=53 && x<=60) + if (fontdata[16*('m')+(y0-8)]&(128>>(x-53))) + indx=255; + } + + if (indx>region.levels) + fprintf(fd,"%c%c%c",0,0,0); + else + { + red=region.color[indx][0]; + green=region.color[indx][1]; + blue=region.color[indx][2]; + + fprintf(fd,"%c%c%c",red,green,blue); + } + } + } + } + + fclose(fd); + fprintf(stdout,"Done!\n"); + fflush(stdout); +} + +void GraphTerrain(struct site source, struct site destination, char *name) +{ + /* This function invokes gnuplot to generate an appropriate + output file indicating the terrain profile between the source + and destination locations when the -p command line option + is used. "basename" is the name assigned to the output + file generated by gnuplot. The filename extension is used + to set gnuplot's terminal setting and output file type. + If no extension is found, .png is assumed. */ + + int x, y, z; + char basename[255], term[30], ext[15]; + double minheight=100000.0, maxheight=-100000.0; + FILE *fd=NULL, *fd1=NULL; + + ReadPath(destination,source); + + fd=fopen("profile.gp","wb"); + + if (clutter>0.0) + fd1=fopen("clutter.gp","wb"); + + for (x=0; xmaxheight) + maxheight=path.elevation[x]+clutter; + + if (path.elevation[x]0 && x0 && x0 && name[x]!='.'; x--); + + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + { + ext[z-(x+1)]=tolower(name[z]); + term[z-(x+1)]=name[z]; + } + + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + term[z-(x+1)]=0; + basename[x]=0; + } + + if (ext[0]==0) /* No extension -- Default is png */ + { + strncpy(term,"png\0",4); + strncpy(ext,"png\0",4); + } + } + + /* Either .ps or .postscript may be used + as an extension for postscript output. */ + + if (strncmp(term,"postscript",10)==0) + strncpy(ext,"ps\0",3); + + else if (strncmp(ext,"ps",2)==0) + strncpy(term,"postscript enhanced color\0",26); + + minheight-=(0.01*maxheight); + + fd=fopen("splat.gp","w"); + fprintf(fd,"set grid\n"); + fprintf(fd,"set yrange [%2.3f to %2.3f]\n", metric?minheight*METERS_PER_FOOT:minheight, metric?maxheight*METERS_PER_FOOT:maxheight); + fprintf(fd,"set encoding iso_8859_1\n"); + fprintf(fd,"set term %s\n",term); + fprintf(fd,"set title \"%s Terrain Profile Between %s and %s (%.2f%c Azimuth)\"\n",splat_name,destination.name, source.name, Azimuth(destination,source),176); + + if (metric) + { + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination)); + fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (meters)\"\n"); + } + + else + { + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination)); + fprintf(fd,"set ylabel \"Ground Elevation Above Sea Level (feet)\"\n"); + } + + fprintf(fd,"set output \"%s.%s\"\n",basename,ext); + + if (clutter>0.0) + { + if (metric) + fprintf(fd,"plot \"profile.gp\" title \"Terrain Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f meters)\" with lines\n",clutter*METERS_PER_FOOT); + else + fprintf(fd,"plot \"profile.gp\" title \"Terrain Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f feet)\" with lines\n",clutter); + } + + else + fprintf(fd,"plot \"profile.gp\" title \"\" with lines\n"); + + fclose(fd); + + x=system("gnuplot splat.gp"); + + if (x!=-1) + { + if (gpsav==0) + { + unlink("splat.gp"); + unlink("profile.gp"); + } + + fprintf(stdout,"Terrain plot written to: \"%s.%s\"\n",basename,ext); + fflush(stdout); + } + + else + fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); +} + +void GraphElevation(struct site source, struct site destination, char *name) +{ + /* This function invokes gnuplot to generate an appropriate + output file indicating the terrain elevation profile between + the source and destination locations when the -e command line + option is used. "basename" is the name assigned to the output + file generated by gnuplot. The filename extension is used + to set gnuplot's terminal setting and output file type. + If no extension is found, .png is assumed. */ + + int x, y, z; + char basename[255], term[30], ext[15]; + double angle, clutter_angle=0.0, refangle, maxangle=-90.0, + minangle=90.0, distance; + struct site remote, remote2; + FILE *fd=NULL, *fd1=NULL, *fd2=NULL; + + ReadPath(destination,source); /* destination=RX, source=TX */ + refangle=ElevationAngle(destination,source); + distance=Distance(source,destination); + + fd=fopen("profile.gp","wb"); + + if (clutter>0.0) + fd1=fopen("clutter.gp","wb"); + + fd2=fopen("reference.gp","wb"); + + for (x=1; x0.0) + { + remote2.lat=path.lat[x]; + remote2.lon=path.lon[x]; + + if (path.elevation[x]!=0.0) + remote2.alt=clutter; + else + remote2.alt=0.0; + + clutter_angle=ElevationAngle(destination,remote2); + } + + if (metric) + { + fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],angle); + + if (fd1!=NULL) + fprintf(fd1,"%f\t%f\n",KM_PER_MILE*path.distance[x],clutter_angle); + + fprintf(fd2,"%f\t%f\n",KM_PER_MILE*path.distance[x],refangle); + } + + else + { + fprintf(fd,"%f\t%f\n",path.distance[x],angle); + + if (fd1!=NULL) + fprintf(fd1,"%f\t%f\n",path.distance[x],clutter_angle); + + fprintf(fd2,"%f\t%f\n",path.distance[x],refangle); + } + + if (angle>maxangle) + maxangle=angle; + + if (clutter_angle>maxangle) + maxangle=clutter_angle; + + if (angle0 && name[x]!='.'; x--); + + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + { + ext[z-(x+1)]=tolower(name[z]); + term[z-(x+1)]=name[z]; + } + + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + term[z-(x+1)]=0; + basename[x]=0; + } + + if (ext[0]==0) /* No extension -- Default is png */ + { + strncpy(term,"png\0",4); + strncpy(ext,"png\0",4); + } + } + + /* Either .ps or .postscript may be used + as an extension for postscript output. */ + + if (strncmp(term,"postscript",10)==0) + strncpy(ext,"ps\0",3); + + else if (strncmp(ext,"ps",2)==0) + strncpy(term,"postscript enhanced color\0",26); + + fd=fopen("splat.gp","w"); + + fprintf(fd,"set grid\n"); + + if (distance>2.0) + fprintf(fd,"set yrange [%2.3f to %2.3f]\n", (-fabs(refangle)-0.25), maxangle+0.25); + else + fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minangle, refangle+(-minangle/8.0)); + + fprintf(fd,"set encoding iso_8859_1\n"); + fprintf(fd,"set term %s\n",term); + fprintf(fd,"set title \"%s Elevation Profile Between %s and %s (%.2f%c azimuth)\"\n",splat_name,destination.name,source.name,Azimuth(destination,source),176); + + if (metric) + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*distance); + else + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,distance); + + + fprintf(fd,"set ylabel \"Elevation Angle Along LOS Path Between\\n%s and %s (degrees)\"\n",destination.name,source.name); + fprintf(fd,"set output \"%s.%s\"\n",basename,ext); + + if (clutter>0.0) + { + if (metric) + fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f meters)\" with lines, \"reference.gp\" title \"Line of Sight Path (%.2f%c elevation)\" with lines\n",clutter*METERS_PER_FOOT,refangle,176); + else + fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"clutter.gp\" title \"Clutter Profile (%.2f feet)\" with lines, \"reference.gp\" title \"Line of Sight Path (%.2f%c elevation)\" with lines\n",clutter,refangle,176); + } + + else + fprintf(fd,"plot \"profile.gp\" title \"Real Earth Profile\" with lines, \"reference.gp\" title \"Line of Sight Path (%.2f%c elevation)\" with lines\n",refangle,176); + + fclose(fd); + + x=system("gnuplot splat.gp"); + + if (x!=-1) + { + if (gpsav==0) + { + unlink("splat.gp"); + unlink("profile.gp"); + unlink("reference.gp"); + + if (clutter>0.0) + unlink("clutter.gp"); + } + + fprintf(stdout,"Elevation plot written to: \"%s.%s\"\n",basename,ext); + fflush(stdout); + } + + else + fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); +} + +void GraphHeight(struct site source, struct site destination, char *name, unsigned char fresnel_plot, unsigned char normalized) +{ + /* This function invokes gnuplot to generate an appropriate + output file indicating the terrain height profile between + the source and destination locations referenced to the + line-of-sight path between the receive and transmit sites + when the -h or -H command line option is used. "basename" + is the name assigned to the output file generated by gnuplot. + The filename extension is used to set gnuplot's terminal + setting and output file type. If no extension is found, + .png is assumed. */ + + int x, y, z; + char basename[255], term[30], ext[15]; + double a, b, c, height=0.0, refangle, cangle, maxheight=-100000.0, + minheight=100000.0, lambda=0.0, f_zone=0.0, fpt6_zone=0.0, + nm=0.0, nb=0.0, ed=0.0, es=0.0, r=0.0, d=0.0, d1=0.0, + terrain, azimuth, distance, dheight=0.0, minterrain=100000.0, + minearth=100000.0, miny, maxy, min2y, max2y; + struct site remote; + FILE *fd=NULL, *fd1=NULL, *fd2=NULL, *fd3=NULL, *fd4=NULL, *fd5=NULL; + + ReadPath(destination,source); /* destination=RX, source=TX */ + azimuth=Azimuth(destination,source); + distance=Distance(destination,source); + refangle=ElevationAngle(destination,source); + b=GetElevation(destination)+destination.alt+earthradius; + + /* Wavelength and path distance (great circle) in feet. */ + + if (fresnel_plot) + { + lambda=9.8425e8/(LR.frq_mhz*1e6); + d=5280.0*path.distance[path.length-1]; + } + + if (normalized) + { + ed=GetElevation(destination); + es=GetElevation(source); + nb=-destination.alt-ed; + nm=(-source.alt-es-nb)/(path.distance[path.length-1]); + } + + fd=fopen("profile.gp","wb"); + + if (clutter>0.0) + fd1=fopen("clutter.gp","wb"); + + fd2=fopen("reference.gp","wb"); + fd5=fopen("curvature.gp", "wb"); + + if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + fd3=fopen("fresnel.gp", "wb"); + fd4=fopen("fresnel_pt_6.gp", "wb"); + } + + for (x=0; x=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + d1=5280.0*path.distance[x]; + f_zone=-1.0*sqrt(lambda*d1*(d-d1)/d); + fpt6_zone=f_zone*fzone_clearance; + } + + if (normalized) + { + r=-(nm*path.distance[x])-nb; + height+=r; + + if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + f_zone+=r; + fpt6_zone+=r; + } + } + + else + r=0.0; + + if (metric) + { + fprintf(fd,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*height); + + if (fd1!=NULL && x>0 && x0 && x=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + if (metric) + { + fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*f_zone); + fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[x],METERS_PER_FOOT*fpt6_zone); + } + + else + { + fprintf(fd3,"%f\t%f\n",path.distance[x],f_zone); + fprintf(fd4,"%f\t%f\n",path.distance[x],fpt6_zone); + } + + if (f_zonemaxheight) + maxheight=height+clutter; + + if (heightmaxheight) + maxheight=r; + + if (terrain=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + if (metric) + { + fprintf(fd3,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r); + fprintf(fd4,"%f\t%f\n",KM_PER_MILE*path.distance[path.length-1],METERS_PER_FOOT*r); + } + + else + { + fprintf(fd3,"%f\t%f\n",path.distance[path.length-1],r); + fprintf(fd4,"%f\t%f\n",path.distance[path.length-1],r); + } + } + + if (r>maxheight) + maxheight=r; + + if (r=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + fclose(fd3); + fclose(fd4); + } + + if (name[0]=='.') + { + /* Default filename and output file type */ + + strncpy(basename,"profile\0",8); + strncpy(term,"png\0",4); + strncpy(ext,"png\0",4); + } + + else + { + /* Extract extension and terminal type from "name" */ + + ext[0]=0; + y=strlen(name); + strncpy(basename,name,254); + + for (x=y-1; x>0 && name[x]!='.'; x--); + + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + { + ext[z-(x+1)]=tolower(name[z]); + term[z-(x+1)]=name[z]; + } + + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + term[z-(x+1)]=0; + basename[x]=0; + } + + if (ext[0]==0) /* No extension -- Default is png */ + { + strncpy(term,"png\0",4); + strncpy(ext,"png\0",4); + } + } + + /* Either .ps or .postscript may be used + as an extension for postscript output. */ + + if (strncmp(term,"postscript",10)==0) + strncpy(ext,"ps\0",3); + + else if (strncmp(ext,"ps",2)==0) + strncpy(term,"postscript enhanced color\0",26); + + fd=fopen("splat.gp","w"); + + dheight=maxheight-minheight; + miny=minheight-0.15*dheight; + maxy=maxheight+0.05*dheight; + + if (maxy<20.0) + maxy=20.0; + + dheight=maxheight-minheight; + min2y=miny-minterrain+0.05*dheight; + + if (minearth=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + fprintf(fd,"set title \"%s Path Profile Between %s and %s (%.2f%c azimuth)\\nWith First Fresnel Zone\"\n",splat_name, destination.name, source.name, azimuth,176); + + else + fprintf(fd,"set title \"%s Height Profile Between %s and %s (%.2f%c azimuth)\"\n",splat_name, destination.name, source.name, azimuth,176); + + if (metric) + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(source,destination)); + else + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(source,destination)); + + if (normalized) + { + if (metric) + fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (meters)\"\n",destination.name,source.name); + + else + fprintf(fd,"set ylabel \"Normalized Height Referenced To LOS Path Between\\n%s and %s (feet)\"\n",destination.name,source.name); + + } + + else + { + if (metric) + fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between\\n%s and %s (meters)\"\n",destination.name,source.name); + + else + fprintf(fd,"set ylabel \"Height Referenced To LOS Path Between\\n%s and %s (feet)\"\n",destination.name,source.name); + } + + fprintf(fd,"set output \"%s.%s\"\n",basename,ext); + + if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + if (clutter>0.0) + { + if (metric) + fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f meters)\" with lines, \"reference.gp\" title \"Line of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines, \"fresnel.gp\" axes x1y1 title \"First Fresnel Zone (%.3f MHz)\" with lines, \"fresnel_pt_6.gp\" title \"%.0f%% of First Fresnel Zone\" with lines\n",clutter*METERS_PER_FOOT,LR.frq_mhz,fzone_clearance*100.0); + else + fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f feet)\" with lines, \"reference.gp\" title \"Line of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines, \"fresnel.gp\" axes x1y1 title \"First Fresnel Zone (%.3f MHz)\" with lines, \"fresnel_pt_6.gp\" title \"%.0f%% of First Fresnel Zone\" with lines\n",clutter,LR.frq_mhz,fzone_clearance*100.0); + } + + else + fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"reference.gp\" title \"Line of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines, \"fresnel.gp\" axes x1y1 title \"First Fresnel Zone (%.3f MHz)\" with lines, \"fresnel_pt_6.gp\" title \"%.0f%% of First Fresnel Zone\" with lines\n",LR.frq_mhz,fzone_clearance*100.0); + } + + else + { + if (clutter>0.0) + { + if (metric) + fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f meters)\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines\n",clutter*METERS_PER_FOOT); + else + fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"clutter.gp\" title \"Ground Clutter (%.2f feet)\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines\n",clutter); + } + + else + fprintf(fd,"plot \"profile.gp\" title \"Point-to-Point Profile\" with lines, \"reference.gp\" title \"Line Of Sight Path\" with lines, \"curvature.gp\" axes x1y2 title \"Earth's Curvature Contour\" with lines\n"); + + } + + fclose(fd); + + x=system("gnuplot splat.gp"); + + if (x!=-1) + { + if (gpsav==0) + { + unlink("splat.gp"); + unlink("profile.gp"); + unlink("reference.gp"); + unlink("curvature.gp"); + + if (fd1!=NULL) + unlink("clutter.gp"); + + if ((LR.frq_mhz>=20.0) && (LR.frq_mhz<=20000.0) && fresnel_plot) + { + unlink("fresnel.gp"); + unlink("fresnel_pt_6.gp"); + } + } + + fprintf(stdout,"\nHeight plot written to: \"%s.%s\"",basename,ext); + fflush(stdout); + } + + else + fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); +} + +void ObstructionAnalysis(struct site xmtr, struct site rcvr, double f, FILE *outfile) +{ + /* Perform an obstruction analysis along the + path between receiver and transmitter. */ + + int x; + struct site site_x; + double h_r, h_t, h_x, h_r_orig, cos_tx_angle, cos_test_angle, + cos_tx_angle_f1, cos_tx_angle_fpt6, d_tx, d_x, + h_r_f1, h_r_fpt6, h_f, h_los, lambda=0.0; + char string[255], string_fpt6[255], string_f1[255]; + + ReadPath(xmtr,rcvr); + h_r=GetElevation(rcvr)+rcvr.alt+earthradius; + h_r_f1=h_r; + h_r_fpt6=h_r; + h_r_orig=h_r; + h_t=GetElevation(xmtr)+xmtr.alt+earthradius; + d_tx=5280.0*Distance(rcvr,xmtr); + cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx); + cos_tx_angle_f1=cos_tx_angle; + cos_tx_angle_fpt6=cos_tx_angle; + + if (f) + lambda=9.8425e8/(f*1e6); + + if (clutter>0.0) + { + fprintf(outfile,"Terrain has been raised by"); + + if (metric) + fprintf(outfile," %.2f meters",METERS_PER_FOOT*clutter); + else + fprintf(outfile," %.2f feet",clutter); + + fprintf(outfile," to account for ground clutter.\n\n"); + } + + /* At each point along the path calculate the cosine + of a sort of "inverse elevation angle" at the receiver. + From the antenna, 0 deg. looks at the ground, and 90 deg. + is parallel to the ground. + + Start at the receiver. If this is the lowest antenna, + then terrain obstructions will be nearest to it. (Plus, + that's the way SPLAT!'s original los() did it.) + + Calculate cosines only. That's sufficient to compare + angles and it saves the extra computational burden of + acos(). However, note the inverted comparison: if + acos(A) > acos(B), then B > A. */ + + for (x=path.length-1; x>0; x--) + { + site_x.lat=path.lat[x]; + site_x.lon=path.lon[x]; + site_x.alt=0.0; + + h_x=GetElevation(site_x)+earthradius+clutter; + d_x=5280.0*Distance(rcvr,site_x); + + /* Deal with the LOS path first. */ + + cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x); + + if (cos_tx_angle>cos_test_angle) + { + if (h_r==h_r_orig) + fprintf(outfile,"Between %s and %s, %s detected obstructions at:\n\n",rcvr.name,xmtr.name,splat_name); + + if (site_x.lat>=0.0) + { + if (metric) + fprintf(outfile," %8.4f N,%9.4f W, %5.2f kilometers, %6.2f meters AMSL\n",site_x.lat, site_x.lon, KM_PER_MILE*(d_x/5280.0), METERS_PER_FOOT*(h_x-earthradius)); + else + fprintf(outfile," %8.4f N,%9.4f W, %5.2f miles, %6.2f feet AMSL\n",site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius); + } + + else + { + if (metric) + fprintf(outfile," %8.4f S,%9.4f W, %5.2f kilometers, %6.2f meters AMSL\n",-site_x.lat, site_x.lon, KM_PER_MILE*(d_x/5280.0), METERS_PER_FOOT*(h_x-earthradius)); + else + + fprintf(outfile," %8.4f S,%9.4f W, %5.2f miles, %6.2f feet AMSL\n",-site_x.lat, site_x.lon, d_x/5280.0, h_x-earthradius); + } + } + + while (cos_tx_angle>cos_test_angle) + { + h_r+=1; + cos_test_angle=((h_r*h_r)+(d_x*d_x)-(h_x*h_x))/(2.0*h_r*d_x); + cos_tx_angle=((h_r*h_r)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r*d_tx); + } + + if (f) + { + /* Now clear the first Fresnel zone... */ + + cos_tx_angle_f1=((h_r_f1*h_r_f1)+(d_tx*d_tx)-(h_t*h_t))/(2.0*h_r_f1*d_tx); + h_los=sqrt(h_r_f1*h_r_f1+d_x*d_x-2*h_r_f1*d_x*cos_tx_angle_f1); + h_f=h_los-sqrt(lambda*d_x*(d_tx-d_x)/d_tx); + + while (h_fh_r_orig) + { + if (metric) + snprintf(string,150,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear all obstructions detected by %s.\n",rcvr.name, METERS_PER_FOOT*(h_r-GetElevation(rcvr)-earthradius),splat_name); + else + snprintf(string,150,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear all obstructions detected by %s.\n",rcvr.name, h_r-GetElevation(rcvr)-earthradius,splat_name); + } + + else + snprintf(string,150,"\nNo obstructions to LOS path due to terrain were detected by %s\n",splat_name); + + if (f) + { + if (h_r_fpt6>h_r_orig) + { + if (metric) + snprintf(string_fpt6,150,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear %.0f%c of the first Fresnel zone.\n",rcvr.name, METERS_PER_FOOT*(h_r_fpt6-GetElevation(rcvr)-earthradius),fzone_clearance*100.0,37); + + else + snprintf(string_fpt6,150,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear %.0f%c of the first Fresnel zone.\n",rcvr.name, h_r_fpt6-GetElevation(rcvr)-earthradius,fzone_clearance*100.0,37); + } + + else + snprintf(string_fpt6,150,"\n%.0f%c of the first Fresnel zone is clear.\n",fzone_clearance*100.0,37); + + if (h_r_f1>h_r_orig) + { + if (metric) + snprintf(string_f1,150,"\nAntenna at %s must be raised to at least %.2f meters AGL\nto clear the first Fresnel zone.\n",rcvr.name, METERS_PER_FOOT*(h_r_f1-GetElevation(rcvr)-earthradius)); + + else + snprintf(string_f1,150,"\nAntenna at %s must be raised to at least %.2f feet AGL\nto clear the first Fresnel zone.\n",rcvr.name, h_r_f1-GetElevation(rcvr)-earthradius); + + } + + else + snprintf(string_f1,150,"\nThe first Fresnel zone is clear.\n"); + } + + fprintf(outfile,"%s",string); + + if (f) + { + fprintf(outfile,"%s",string_f1); + fprintf(outfile,"%s",string_fpt6); + } +} + +void PathReport(struct site source, struct site destination, char *name, char graph_it) +{ + /* This function writes a SPLAT! Path Report (name.txt) to + the filesystem. If (graph_it == 1), then gnuplot is invoked + to generate an appropriate output file indicating the Longley-Rice + model loss between the source 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, .png is assumed. */ + + int x, y, z, errnum; + char basename[255], term[30], ext[15], strmode[100], + report_name[80], block=0; + double maxloss=-100000.0, minloss=100000.0, loss, haavt, + angle1, angle2, azimuth, pattern=1.0, patterndB=0.0, + total_loss=0.0, cos_xmtr_angle, cos_test_angle=0.0, + source_alt, test_alt, dest_alt, source_alt2, dest_alt2, + distance, elevation, four_thirds_earth, field_strength, + free_space_loss=0.0, eirp=0.0, voltage, rxp, dBm, + power_density; + FILE *fd=NULL, *fd2=NULL; + + sprintf(report_name,"%s-to-%s.txt",source.name,destination.name); + + four_thirds_earth=FOUR_THIRDS*EARTHRADIUS; + + 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]='_'; + + fd2=fopen(report_name,"w"); + + fprintf(fd2,"\n\t\t--==[ %s v%s Path Analysis ]==--\n\n",splat_name,splat_version); + fprintf(fd2,"%s\n\n",dashes); + fprintf(fd2,"Transmitter site: %s\n",source.name); + + if (source.lat>=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)); + + 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) + { + 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); + } + + if (metric) + fprintf(fd2,"Distance to %s: %.2f kilometers\n",destination.name,KM_PER_MILE*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); + + if (angle1>=0.0) + fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",destination.name,angle1); + + else + fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",destination.name,angle1); + + if ((angle2-angle1)>0.0001) + { + if (angle2<0.0) + fprintf(fd2,"Depression"); + else + fprintf(fd2,"Elevation"); + + fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2); + } + + fprintf(fd2,"\n%s\n\n",dashes); + + /* Receiver */ + + fprintf(fd2,"Receiver site: %s\n",destination.name); + + 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)); + + 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) + { + 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); + + if (angle1>=0.0) + fprintf(fd2,"Elevation angle to %s: %+.4f degrees\n",source.name,angle1); + + else + fprintf(fd2,"Depression angle to %s: %+.4f degrees\n",source.name,angle1); + + if ((angle2-angle1)>0.0001) + { + if (angle2<0.0) + fprintf(fd2,"Depression"); + else + fprintf(fd2,"Elevation"); + + fprintf(fd2," angle to the first obstruction: %+.4f degrees\n",angle2); + } + + fprintf(fd2,"\n%s\n\n",dashes); + + if (LR.frq_mhz>0.0) + { + fprintf(fd2,"Longley-Rice path calculation parameters used in this analysis:\n\n"); + fprintf(fd2,"Earth's Dielectric Constant: %.3lf\n",LR.eps_dielect); + fprintf(fd2,"Earth's Conductivity: %.3lf Siemens/meter\n",LR.sgm_conductivity); + fprintf(fd2,"Atmospheric Bending Constant (N-units): %.3lf ppm\n",LR.eno_ns_surfref); + fprintf(fd2,"Frequency: %.3lf MHz\n",LR.frq_mhz); + fprintf(fd2,"Radio Climate: %d (",LR.radio_climate); + + switch (LR.radio_climate) + { + case 1: + fprintf(fd2,"Equatorial"); + break; + + case 2: + fprintf(fd2,"Continental Subtropical"); + break; + + case 3: + fprintf(fd2,"Maritime Subtropical"); + break; + + case 4: + fprintf(fd2,"Desert"); + break; + + case 5: + fprintf(fd2,"Continental Temperate"); + break; + + case 6: + fprintf(fd2,"Martitime Temperate, Over Land"); + break; + + case 7: + fprintf(fd2,"Maritime Temperate, Over Sea"); + break; + + default: + fprintf(fd2,"Unknown"); + } + + fprintf(fd2,")\nPolarization: %d (",LR.pol); + + if (LR.pol==0) + fprintf(fd2,"Horizontal"); + + if (LR.pol==1) + fprintf(fd2,"Vertical"); + + 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); + + if (LR.erp!=0.0) + { + fprintf(fd2,"Transmitter ERP: "); + + if (LR.erp<1.0) + fprintf(fd2,"%.1lf milliwatts",1000.0*LR.erp); + + if (LR.erp>=1.0 && LR.erp<10.0) + fprintf(fd2,"%.1lf Watts",LR.erp); + + if (LR.erp>=10.0 && LR.erp<10.0e3) + fprintf(fd2,"%.0lf Watts",LR.erp); + + if (LR.erp>=10.0e3) + fprintf(fd2,"%.3lf kilowatts",LR.erp/1.0e3); + + dBm=10.0*(log10(LR.erp*1000.0)); + fprintf(fd2," (%+.2f dBm)\n",dBm); + + /* EIRP = ERP + 2.14 dB */ + + fprintf(fd2,"Transmitter EIRP: "); + + eirp=LR.erp*1.636816521; + + if (eirp<1.0) + fprintf(fd2,"%.1lf milliwatts",1000.0*eirp); + + if (eirp>=1.0 && eirp<10.0) + fprintf(fd2,"%.1lf Watts",eirp); + + if (eirp>=10.0 && eirp<10.0e3) + fprintf(fd2,"%.0lf Watts",eirp); - int x, y, z; - char filename[255], term[15], ext[15]; - double angle, refangle, maxangle=-90.0; - struct site remote; - FILE *fd=NULL, *fd2=NULL; + if (eirp>=10.0e3) + fprintf(fd2,"%.3lf kilowatts",eirp/1.0e3); - ReadPath(destination,source); /* destination=RX, source=TX */ - refangle=ElevationAngle(destination,source); + dBm=10.0*(log10(eirp*1000.0)); + fprintf(fd2," (%+.2f dBm)\n",dBm); + } - fd=fopen("profile.gp","wb"); - fd2=fopen("reference.gp","wb"); + fprintf(fd2,"\n%s\n\n",dashes); - for (x=1; xmaxangle) - maxangle=angle; - } + if (patterndB!=0.0) + fprintf(fd2,"%s antenna pattern towards %s: %.3f (%.2f dB)\n", source.name, destination.name, pattern, patterndB); - fprintf(fd,"%f\t%f\n",path.distance[path.length-1],refangle); - fprintf(fd2,"%f\t%f\n",path.distance[path.length-1],refangle); + ReadPath(source, destination); /* source=TX, destination=RX */ - fclose(fd); - fclose(fd2); + /* Copy elevations plus clutter along + path into the elev[] array. */ - if (name[0]==0) - { - /* Default filename and output file type */ + for (x=1; x=cos_test_angle) + block=1; + } + + /* At this point, we have the elevation angle + to the first obstruction (if it exists). */ } - ext[y]=0; - term[y]=0; - filename[z]=0; - } + /* 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. */ - else - { /* No extension -- Default is gif */ + elev[0]=y-1; /* (number of points - 1) */ - filename[x]=0; - strncpy(term,"gif\0",4); - strncpy(ext,"gif\0",4); - } - } + /* Distance between elevation samples */ - /* Either .ps or .postscript may be used - as an extension for postscript output. */ + elev[1]=METERS_PER_MILE*(path.distance[y]-path.distance[y-1]); - if (strncmp(term,"postscript",10)==0) - strncpy(ext,"ps\0",3); + point_to_point(elev, 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); - else if (strncmp(ext,"ps",2)==0) - strncpy(term,"postscript\0",11); + if (block) + elevation=((acos(cos_test_angle))/DEG2RAD)-90.0; + else + elevation=((acos(cos_xmtr_angle))/DEG2RAD)-90.0; - fprintf(stdout,"Writing \"%s.%s\"...",filename,ext); - fflush(stdout); + /* Integrate the antenna's radiation + pattern into the overall path loss. */ - fd=fopen("splat.gp","w"); + x=(int)rint(10.0*(10.0-elevation)); - 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 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 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"); + if (x>=0 && x<=1000) + { + pattern=(double)LR.antenna_pattern[(int)azimuth][x]; - fclose(fd); - - x=system("gnuplot splat.gp"); + if (pattern!=0.0) + patterndB=20.0*log10(pattern); + } - if (x!=-1) - { - unlink("splat.gp"); - unlink("profile.gp"); - unlink("reference.gp"); + else + patterndB=0.0; - fprintf(stdout," Done!\n"); - fflush(stdout); - } + total_loss=loss-patterndB; - else - fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); -} + if (metric) + fprintf(fd,"%f\t%f\n",KM_PER_MILE*(path.distance[path.length-1]-path.distance[y]),total_loss); -void GraphHeight(struct site source, struct site destination, char *name) -{ - /* This function invokes gnuplot to generate an appropriate - output file indicating the terrain profile between the source - and destination locations. 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. */ + else + fprintf(fd,"%f\t%f\n",path.distance[path.length-1]-path.distance[y],total_loss); - 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; + if (total_loss>maxloss) + maxloss=total_loss; - ReadPath(destination,source); /* destination=RX, source=TX */ - refangle=ElevationAngle(destination,source); - b=GetElevation(destination)+destination.alt+earthradius; + if (total_lossmaxheight) - maxheight=height; + if (patterndB!=0.0) + fprintf(fd2,"Total path loss including %s antenna pattern: %.2f dB\n",source.name,total_loss); - if (height0 && name[x]!='.'; x--); + + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + { + ext[z-(x+1)]=tolower(name[z]); + term[z-(x+1)]=name[z]; + } - filename[x]=0; - strncpy(term,"gif\0",4); - strncpy(ext,"gif\0",4); + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + term[z-(x+1)]=0; + basename[x]=0; + } } - } - /* Either .ps or .postscript may be used - as an extension for postscript output. */ + if (ext[0]==0) /* No extension -- Default is png */ + { + strncpy(term,"png\0",4); + strncpy(ext,"png\0",4); + } - if (strncmp(term,"postscript",10)==0) - strncpy(ext,"ps\0",3); + /* Either .ps or .postscript may be used + as an extension for postscript output. */ - else if (strncmp(ext,"ps",2)==0) - strncpy(term,"postscript\0",11); + if (strncmp(term,"postscript",10)==0) + strncpy(ext,"ps\0",3); - fprintf(stdout,"Writing \"%s.%s\"...",filename,ext); - fflush(stdout); + else if (strncmp(ext,"ps",2)==0) + strncpy(term,"postscript enhanced color\0",26); - fd=fopen("splat.gp","w"); + fd=fopen("splat.gp","w"); - minheight-=20.0; - maxheight+=20.0; + fprintf(fd,"set grid\n"); + fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minloss, maxloss); + fprintf(fd,"set encoding iso_8859_1\n"); + fprintf(fd,"set term %s\n",term); + fprintf(fd,"set title \"%s Loss Profile Along Path Between %s and %s (%.2f%c azimuth)\"\n",splat_name, destination.name, source.name, Azimuth(destination,source),176); - if (maxheight<20.0) - maxheight=20.0; + if (metric) + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",destination.name,source.name,KM_PER_MILE*Distance(destination,source)); + else + fprintf(fd,"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",destination.name,source.name,Distance(destination,source)); - fprintf(fd,"set grid\n"); - fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minheight, maxheight); - fprintf(fd,"set term %s\n",term); - fprintf(fd,"set title \"SPLAT! Height Profile\"\n"); - fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name); - fprintf(fd,"set ylabel \"Ground Height Above Path Between %s and %s (feet)\"\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"); + if (got_azimuth_pattern || got_elevation_pattern) + fprintf(fd,"set ylabel \"Total Path Loss (including TX antenna pattern) (dB)"); + else + fprintf(fd,"set ylabel \"Longley-Rice Path Loss (dB)"); - fclose(fd); + fprintf(fd,"\"\nset output \"%s.%s\"\n",basename,ext); + fprintf(fd,"plot \"profile.gp\" title \"Path Loss\" with lines\n"); - x=system("gnuplot splat.gp"); + fclose(fd); + + x=system("gnuplot splat.gp"); - if (x!=-1) - { - unlink("splat.gp"); - unlink("profile.gp"); - unlink("reference.gp"); - fprintf(stdout," Done!\n"); - fflush(stdout); + if (x!=-1) + { + if (gpsav==0) + { + unlink("splat.gp"); + unlink("profile.gp"); + unlink("reference.gp"); + } + + fprintf(stdout,"Path loss plot written to: \"%s.%s\"\n",basename,ext); + fflush(stdout); + } + + else + fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); } - else - fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); + if (x!=-1 && gpsav==0) + unlink("profile.gp"); } -void GraphLongley(struct site source, struct site destination, char *name) +void SiteReport(struct site xmtr) { - /* This function invokes gnuplot to generate an appropriate - output file indicating the Longley-Rice model loss between - the source 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. */ - - int x, y, z, errnum, errflag=0; - char filename[255], term[15], ext[15], strmode[100], report_name[80]; - double maxloss=-100000.0, minloss=100000.0, loss, haavt, angle; - FILE *fd=NULL, *fd2=NULL; + char report_name[80]; + double terrain; + int x, azi; + FILE *fd; - sprintf(report_name,"%s-to-%s.lro",source.name,destination.name); + 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]='_'; - fd2=fopen(report_name,"w"); + fd=fopen(report_name,"w"); - fprintf(fd2,"\n\t--==[ SPLAT! v%s Longley-Rice Model Path Loss Report ]==--\n\n",splat_version); - fprintf(fd2,"Analysis of RF path conditions between %s and %s:\n",source.name, destination.name); - fprintf(fd2,"\n-------------------------------------------------------------------------\n\n"); - fprintf(fd2,"Transmitter site: %s\n",source.name); + fprintf(fd,"\n\t--==[ %s v%s Site Analysis Report For: %s ]==--\n\n",splat_name, splat_version, xmtr.name); - if (source.lat>=0.0) + fprintf(fd,"%s\n\n",dashes); + + if (xmtr.lat>=0.0) { - fprintf(fd2,"Site location: %.4f North / %.4f West",source.lat, source.lon); - fprintf(fd2, " (%s N / ", dec2dms(source.lat)); + fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon); + fprintf(fd, " (%s N / ",dec2dms(xmtr.lat)); } else { - - fprintf(fd2,"Site location: %.4f South / %.4f West",-source.lat, source.lon); - fprintf(fd2, " (%s S / ", dec2dms(source.lat)); + fprintf(fd,"Site location: %.4f South / %.4f West",-xmtr.lat, xmtr.lon); + fprintf(fd, " (%s S / ",dec2dms(xmtr.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)); - - haavt=haat(source); - - if (haavt>-4999.0) - fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt); - 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)); - - angle=ElevationAngle(source,destination); - - if (angle>=0.0) - fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle); - - if (angle<0.0) - fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",source.name,destination.name,angle); - - fprintf(fd2,"\n-------------------------------------------------------------------------\n\n"); - - /* Receiver */ - - fprintf(fd2,"Receiver site: %s\n",destination.name); + fprintf(fd, "%s W)\n",dec2dms(xmtr.lon)); - if (destination.lat>=0.0) + if (metric) { - fprintf(fd2,"Site location: %.4f North / %.4f West",destination.lat, destination.lon); - fprintf(fd2, " (%s N / ", dec2dms(destination.lat)); + 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(fd2,"Site location: %.4f South / %.4f West",-destination.lat, destination.lon); - fprintf(fd2, " (%s S / ", dec2dms(destination.lat)); + 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(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)); + terrain=haat(xmtr); - haavt=haat(destination); + 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); - if (haavt>-4999.0) - fprintf(fd2,"Antenna height above average terrain: %.2f feet\n",haavt); + /* 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. */ - 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)); + 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); + } - angle=ElevationAngle(destination,source); + else + fprintf(fd,"No terrain\n"); + } + } - if (angle>=0.0) - fprintf(fd2,"Angle of elevation between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle); + fprintf(fd,"\n%s\n\n",dashes); + fclose(fd); + fprintf(stdout,"\nSite analysis report written to: \"%s\"\n",report_name); +} - if (angle<0.0) - fprintf(fd2,"Angle of depression between %s and %s: %+.4f degrees.\n",destination.name,source.name,angle); +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. */ - fprintf(fd2,"\n-------------------------------------------------------------------------\n\n"); + int x, y, width, ymin, ymax; - fprintf(fd2,"Longley-Rice path calculation parameters used in this analysis:\n\n"); - fprintf(fd2,"Earth's Dielectric Constant: %.3lf\n",LR.eps_dielect); - fprintf(fd2,"Earth's Conductivity: %.3lf\n",LR.sgm_conductivity); - fprintf(fd2,"Atmospheric Bending Constant (N): %.3lf\n",LR.eno_ns_surfref); - fprintf(fd2,"Frequency: %.3lf (MHz)\n",LR.frq_mhz); - fprintf(fd2,"Radio Climate: %d (",LR.radio_climate); + width=ReduceAngle(max_lon-min_lon); - switch (LR.radio_climate) + if ((max_lon-min_lon)<=180.0) { - case 1: - fprintf(fd2,"Equatorial"); - break; - - case 2: - fprintf(fd2,"Continental Subtropical"); - break; + for (y=0; y<=width; y++) + for (x=min_lat; x<=max_lat; x++) + { + ymin=(int)(min_lon+(double)y); - case 3: - fprintf(fd2,"Maritime Subtropical"); - break; + while (ymin<0) + ymin+=360; - case 4: - fprintf(fd2,"Desert"); - break; + while (ymin>=360) + ymin-=360; - case 5: - fprintf(fd2,"Continental Temperate"); - break; + ymax=ymin+1; - case 6: - fprintf(fd2,"Martitime Temperate, Over Land"); - break; + while (ymax<0) + ymax+=360; - case 7: - fprintf(fd2,"Maritime Temperate, Over Sea"); - break; + while (ymax>=360) + ymax-=360; - default: - fprintf(fd2,"Unknown"); + if (ippd==3600) + snprintf(string,19,"%d:%d:%d:%d-hd",x, x+1, ymin, ymax); + else + snprintf(string,16,"%d:%d:%d:%d",x, x+1, ymin, ymax); + LoadSDF(string); + } } - fprintf(fd2,")\nPolarization: %d (",LR.pol); - - if (LR.pol==0) - fprintf(fd2,"Horizontal"); - - if (LR.pol==1) - fprintf(fd2,"Vertical"); + else + { + for (y=0; y<=width; y++) + for (x=min_lat; x<=max_lat; x++) + { + ymin=max_lon+y; - 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); + while (ymin<0) + ymin+=360; - fprintf(fd2,"\n-------------------------------------------------------------------------\n\n"); + while (ymin>=360) + ymin-=360; + + ymax=ymin+1; - fprintf(fd2,"Analysis Results:\n\n"); + while (ymax<0) + ymax+=360; - ReadPath(source, destination); /* destination=RX, source=TX */ + while (ymax>=360) + ymax-=360; - elev_l[1]=0.04*METERS_PER_MILE; + if (ippd==3600) + snprintf(string,19,"%d:%d:%d:%d-hd",x, x+1, ymin, ymax); + else + snprintf(string,16,"%d:%d:%d:%d",x, x+1, ymin, ymax); + LoadSDF(string); + } + } +} - for (x=1; xmaxloss) - maxloss=loss; + sscanf(string,"%d, %d",&max_north, &min_north); - if (loss255.0) + ano=255.0; - for (x=0; name[x]!='.' && name[x]!=0 && x<254; x++) - filename[x]=name[x]; + PutSignal(latitude,longitude,((unsigned char)round(ano))); + } + } - if (name[x]=='.') - { - for (y=0, z=x, x++; name[x]!=0 && x<254 && y<14; x++, y++) + if (LR.erp!=0.0 && dbm!=0) { - term[y]=tolower(name[x]); - ext[y]=term[y]; - } + /* signal power level in dBm */ - ext[y]=0; - term[y]=0; - filename[z]=0; - } + if (contour_threshold==0 || (ano>=(double)contour_threshold)) + { + ano=200.0+rint(ano); - else - { /* No extension -- Default is gif */ + if (ano<0.0) + ano=0.0; - filename[x]=0; - strncpy(term,"gif\0",4); - strncpy(ext,"gif\0",4); - } - } + if (ano>255.0) + ano=255.0; - /* Either .ps or .postscript may be used - as an extension for postscript output. */ + PutSignal(latitude,longitude,((unsigned char)round(ano))); + } + } - if (strncmp(term,"postscript",10)==0) - strncpy(ext,"ps\0",3); + if (LR.erp!=0.0 && dbm==0) + { + /* field strength dBuV/m */ - else if (strncmp(ext,"ps",2)==0) - strncpy(term,"postscript\0",11); + if (contour_threshold==0 || (ano>=(double)contour_threshold)) + { + ano=100.0+rint(ano); - fprintf(stdout,"Writing \"%s.%s\"...",filename,ext); - fflush(stdout); + if (ano<0.0) + ano=0.0; - fd=fopen("splat.gp","w"); + if (ano>255.0) + ano=255.0; - fprintf(fd,"set grid\n"); - fprintf(fd,"set yrange [%2.3f to %2.3f]\n", minloss, maxloss); - fprintf(fd,"set term %s\n",term); - fprintf(fd,"set title \"SPLAT! Loss Profile\"\n"); - fprintf(fd,"set xlabel \"Distance Between %s and %s (miles)\"\n",destination.name,source.name); - fprintf(fd,"set ylabel \"Longley-Rice Loss (dB)\"\n"); - fprintf(fd,"set output \"%s.%s\"\n",filename,ext); - fprintf(fd,"plot \"profile.gp\" title \"Longley-Rice Loss\" with lines\n"); + PutSignal(latitude,longitude,((unsigned char)round(ano))); + } + } - fclose(fd); - - x=system("gnuplot splat.gp"); + s=fgets(string,78,fd); + sscanf(string,"%lf, %lf, %lf, %lf, %lf",&latitude, &longitude, &azimuth, &elevation, &ano); + } - if (x!=-1) - { - unlink("splat.gp"); - unlink("profile.gp"); - unlink("reference.gp"); + fclose(fd); fprintf(stdout," Done!\n"); fflush(stdout); } else - fprintf(stderr,"\n*** ERROR: Error occurred invoking gnuplot!\n"); + error=1; + + return error; } -void ObstructionReport(struct site xmtr, struct site rcvr, char report) +void WriteKML(struct site source, struct site destination) { - struct site result, result2, new_site; - double angle, haavt; - unsigned char block; - char report_name[80], string[255]; - int x; - 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; + + ReadPath(source,destination); - sprintf(report_name,"%s-to-%s.txt",xmtr.name,rcvr.name); + 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) @@ -3457,267 +7017,340 @@ void ObstructionReport(struct site xmtr, struct site rcvr, char report) fd=fopen(report_name,"w"); - fprintf(fd,"\n\t\t--==[ SPLAT! v%s Obstruction Report ]==--\n\n",splat_version); - fprintf(fd,"Analysis of line-of-sight path conditions between %s and %s:\n",xmtr.name, rcvr.name); - fprintf(fd,"\n-------------------------------------------------------------------------\n\n"); - fprintf(fd,"Transmitter site: %s\n",xmtr.name); - + fprintf(fd,"\n"); + fprintf(fd,"\n"); + fprintf(fd,"\n",splat_name, 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); - if (xmtr.lat>=0.0) - { - fprintf(fd,"Site location: %.4f North / %.4f West",xmtr.lat, xmtr.lon); - fprintf(fd, " (%s N / ", dec2dms(xmtr.lat)); - } + 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,"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)); - - haavt=haat(xmtr); - - if (haavt>-4999.0) - fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt); + fprintf(fd,"
%s South
\n",dec2dms(source.lat)); - 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)); + fprintf(fd,"
%s West
\n",dec2dms(source.lon)); - angle=ElevationAngle(xmtr,rcvr); - - if (angle>=0.0) - fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle); - - if (angle<0.0) - fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",xmtr.name,rcvr.name,angle); - - fprintf(fd,"\n-------------------------------------------------------------------------\n\n"); - - /* Receiver */ - - 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)); - } + azimuth=Azimuth(source,destination); + distance=Distance(source,destination); + if (metric) + fprintf(fd,"
%.2f km",distance*KM_PER_MILE); 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)); - 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) - fprintf(fd,"Antenna height above average terrain: %.2f feet\n",haavt); - - 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)); + 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"); - angle=ElevationAngle(rcvr,xmtr); + if (destination.lat>=0.0) + fprintf(fd,"
%s North
\n",dec2dms(destination.lat)); + else + fprintf(fd,"
%s South
\n",dec2dms(destination.lat)); - if (angle>=0.0) - fprintf(fd,"Angle of elevation between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle); + fprintf(fd,"
%s West
\n",dec2dms(destination.lon)); - if (angle<0.0) - fprintf(fd,"Angle of depression between %s and %s: %+.4f degrees.\n",rcvr.name,xmtr.name,angle); + 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"); - fprintf(fd,"\n-------------------------------------------------------------------------\n\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 */ - if (report=='y') + for (y=0; y=0 && block==0; x--) { - if (result.lat!=result2.lat || result.lon!=result2.lon || result.alt!=result2.alt) - { - if (result.lat>=0.0) - fprintf(fd,"\t%.4f N, %.4f W, %5.2f miles, %6.2f feet AMSL.\n",result.lat, result.lon, Distance(rcvr,result), result.alt); - else - - fprintf(fd,"\t%.4f S, %.4f W, %5.2f miles, %6.2f feet AMSL.\n",-result.lat, result.lon, Distance(rcvr,result), result.alt); - } + distance=5280.0*(path.distance[y]-path.distance[x]); + test_alt=earthradius+path.elevation[x]; - result2=result; - new_site.alt+=1.0; + cos_test_angle=((rx_alt*rx_alt)+(distance*distance)-(test_alt*test_alt))/(2.0*rx_alt*distance); - /* Can you hear me now? :-) */ + /* 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. */ - result=los(xmtr,new_site); - block=result.name[0]; + if (cos_xmtr_angle>=cos_test_angle) + block=1; } - 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); + 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 - sprintf(string,"\nNo obstructions due to terrain were detected by SPLAT!\n\n"); - } - - fprintf(fd,"%s",string); - - fclose(fd); - - /* Display LOS status to terminal */ - - fprintf(stdout,"%sObstruction report written to: \"%s\"\n",string,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," %f,%f,5\n",(path.lon[y]<180.0?-path.lon[y]:360.0-path.lon[y]),path.lat[y]); } - 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)); - - terrain=haat(xmtr); + fprintf(fd," \n"); + fprintf(fd," \n"); + fprintf(fd,"\n"); - if (terrain>-4999.0) - { - fprintf(fd,"Antenna height above average terrain: %.2f feet\n\n",terrain); + 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"); - /* 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. */ + fprintf(fd,"
\n"); + fprintf(fd,"
\n"); - 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); + fclose(fd); - if (terrain>-4999.0) - fprintf(fd,"%.2f feet AMSL\n",terrain); - else - fprintf(fd,"No terrain\n"); - } - } + fprintf(stdout, "\nKML file written to: \"%s\"",report_name); - fprintf(fd,"\n---------------------------------------------------------------------------\n\n"); - fclose(fd); - fprintf(stdout,"\nSite analysis report written to: \"%s\"\n",report_name); + fflush(stdout); } -int main(char argc, char *argv[]) +int main(int argc, char *argv[]) { - int x, y, ymin, ymax, width, 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, min_lat, min_lon, max_lat, max_lon, + rxlat, rxlon, txlat, txlon, west_min, west_max, + north_min, north_max; - unsigned char coverage=0, LRmap=0, ext[20], terrain_plot=0, - elevation_plot=0, height_plot=0, + unsigned char coverage=0, LRmap=0, terrain_plot=0, + elevation_plot=0, height_plot=0, map=0, longley_plot=0, cities=0, bfs=0, txsites=0, - count, report='y'; + norm=0, topomap=0, geo=0, kml=0, pt2pt_mode=0, + area_mode=0, max_txsites, ngs=0, nolospath=0, + nositereports=0, fresnel_plot=1; 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], *env=NULL, - txfile[255], map=0, boundary_file[5][255], - rxsite=0; + txfile[255], boundary_file[5][255], + udt_file[255], rxsite=0, ani_filename[255], + ano_filename[255], ext[20], *s=NULL; double altitude=0.0, altitudeLR=0.0, tx_range=0.0, - rx_range=0.0, deg_range=0.0, deg_limit, + rx_range=0.0, deg_range=0.0, deg_limit=0.0, deg_range_lon, er_mult; - struct site tx_site[4], rx_site; + struct site tx_site[32], rx_site; FILE *fd; - sprintf(header,"\n\t\t--==[ Welcome To SPLAT! v%s ]==--\n\n", splat_version); + strncpy(splat_version,"1.3.0\0",6); + + if (HD_MODE==1) + strncpy(splat_name,"SPLAT! HD\0",10); + else + strncpy(splat_name,"SPLAT!\0",7); + + strncpy(dashes,"---------------------------------------------------------------------------\0",76); 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"); - fprintf(stdout,"\t-db maximum loss contour to display on path loss maps (80-230 dB)\n\n"); + fprintf(stdout,"\n\t\t --==[ %s v%s Available Options... ]==--\n\n",splat_name, splat_version); + + fprintf(stdout," -t txsite(s).qth (max of 4 with -c, max of 30 with -L)\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 (5 max)\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 path loss 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," -m earth radius multiplier\n"); + fprintf(stdout," -n do not plot LOS paths in .ppm maps\n"); + fprintf(stdout," -N do not produce unnecessary site or obstruction reports\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 threshold beyond which contours will not be displayed\n"); + fprintf(stdout," -nf do not plot Fresnel zones in height plots\n"); + fprintf(stdout," -fz Fresnel zone clearance percentage (default = 60)\n"); + fprintf(stdout," -gc ground clutter height (feet/meters)\n"); + fprintf(stdout," -ngs display greyscale topography as white in .ppm files\n"); + fprintf(stdout," -erp override ERP in .lrp file (Watts)\n"); + fprintf(stdout," -ano name of alphanumeric output file\n"); + fprintf(stdout," -ani name of alphanumeric input file\n"); + fprintf(stdout," -udt name of user defined terrain input file\n"); + fprintf(stdout," -kml generate Google Earth (.kml) compatible output\n"); + fprintf(stdout," -geo generate an Xastir .geo georeference file (with .ppm output)\n"); + fprintf(stdout," -dbm plot signal power level contours rather than field strength\n"); + fprintf(stdout," -gpsav preserve gnuplot temporary working files after SPLAT! execution\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"); + + if (HD_MODE==0) + fprintf(stdout,"\n\tsplat | less\n\n"); + else + fprintf(stdout,"\n\tsplat-hd | less\n\n"); fprintf(stdout,"Type 'man splat', or see the documentation for more details.\n\n"); + + y=(int)sqrt((int)MAXPAGES); + + fprintf(stdout,"This compilation of %s supports analysis over a region of\n%d square ",splat_name,y); + + if (y==1) + + fprintf(stdout,"degree"); + else + fprintf(stdout,"degrees"); + + fprintf(stdout," of terrain.\n\n"); fflush(stdout); + return 1; } y=argc-1; + kml=0; + geo=0; + dbm=0; + gpsav=0; + metric=0; rxfile[0]=0; txfile[0]=0; string[0]=0; mapfile[0]=0; + clutter=0.0; + forced_erp=-1.0; + forced_freq=0.0; elevation_file[0]=0; terrain_file[0]=0; sdf_path[0]=0; + udt_file[0]=0; path.length=0; + max_txsites=30; + fzone_clearance=0.6; + contour_threshold=0; rx_site.lat=91.0; rx_site.lon=361.0; + longley_file[0]=0; + ano_filename[0]=0; + ani_filename[0]=0; earthradius=EARTHRADIUS; + ippd=IPPD; /* pixels per degree (integer) */ + ppd=(double)ippd; /* pixels per degree (double) */ + dpp=1.0/ppd; /* degrees per pixel */ + mpi=ippd-1; /* maximum pixel index per degree */ + + sprintf(header,"\n\t\t--==[ Welcome To %s v%s ]==--\n\n", splat_name, splat_version); + for (x=0; x<4; x++) { tx_site[x].lat=91.0; tx_site[x].lon=361.0; } - for (x=0; x100.0) + fzone_clearance=60.0; + + fzone_clearance/=100.0; + } + } + if (strcmp(argv[x],"-o")==0) { z=x+1; @@ -3775,6 +7435,14 @@ int main(char argc, char *argv[]) map=1; } + if (strcmp(argv[x],"-udt")==0) + { + z=x+1; + + if (z<=y && argv[z][0] && argv[z][0]!='-') + strncpy(udt_file,argv[z],253); + } + if (strcmp(argv[x],"-c")==0) { z=x+1; @@ -3782,26 +7450,19 @@ int main(char argc, char *argv[]) if (z<=y && argv[z][0] && argv[z][0]!='-') { sscanf(argv[z],"%lf",&altitude); + map=1; coverage=1; + area_mode=1; + max_txsites=4; } } - if (strcmp(argv[x],"-db")==0) + if (strcmp(argv[x],"-db")==0 || strcmp(argv[x],"-dB")==0) { z=x+1; - if (z<=y && argv[z][0] && argv[z][0]!='-') - { - sscanf(argv[z],"%d",&maxdB); - - maxdB=abs(maxdB); - - if (maxdB<80) - maxdB=80; - - if (maxdB>230) - maxdB=230; - } + if (z<=y && argv[z][0]) /* A minus argument is legal here */ + sscanf(argv[z],"%d",&contour_threshold); } if (strcmp(argv[x],"-p")==0) @@ -3812,6 +7473,7 @@ int main(char argc, char *argv[]) { strncpy(terrain_file,argv[z],253); terrain_plot=1; + pt2pt_mode=1; } } @@ -3823,10 +7485,11 @@ int main(char argc, char *argv[]) { strncpy(elevation_file,argv[z],253); elevation_plot=1; + pt2pt_mode=1; } } - if (strcmp(argv[x],"-h")==0) + if (strcmp(argv[x],"-h")==0 || strcmp(argv[x],"-H")==0) { z=x+1; @@ -3834,25 +7497,43 @@ int main(char argc, char *argv[]) { strncpy(height_file,argv[z],253); height_plot=1; + pt2pt_mode=1; } + + if (strcmp(argv[x],"-H")==0) + norm=1; + else + norm=0; } + if (strcmp(argv[x],"-metric")==0) + metric=1; + + if (strcmp(argv[x],"-gpsav")==0) + gpsav=1; + + if (strcmp(argv[x],"-geo")==0) + geo=1; + + if (strcmp(argv[x],"-kml")==0) + kml=1; + + if (strcmp(argv[x],"-nf")==0) + fresnel_plot=0; + + if (strcmp(argv[x],"-ngs")==0) + ngs=1; + if (strcmp(argv[x],"-n")==0) - { - if (z<=y && argv[z][0] && argv[z][0]!='-') - { - report='n'; - map=1; - } - } + nolospath=1; + + if (strcmp(argv[x],"-dbm")==0) + dbm=1; if (strcmp(argv[x],"-N")==0) { - if (z<=y && argv[z][0] && argv[z][0]!='-'); - { - report='N'; - map=1; - } + nolospath=1; + nositereports=1; } if (strcmp(argv[x],"-d")==0) @@ -3869,13 +7550,14 @@ int main(char argc, char *argv[]) z=x+1; - while (z<=y && argv[z][0] && argv[z][0]!='-' && txsites<4) + while (z<=y && argv[z][0] && argv[z][0]!='-' && txsites<30) { strncpy(txfile,argv[z],253); tx_site[txsites]=LoadQTH(txfile); txsites++; z++; } + z--; } @@ -3886,14 +7568,12 @@ int main(char argc, char *argv[]) if (z<=y && argv[z][0] && argv[z][0]!='-') { sscanf(argv[z],"%lf",&altitudeLR); + map=1; + LRmap=1; + area_mode=1; if (coverage) fprintf(stdout,"c and L are exclusive options, ignoring L.\n"); - else - { - LRmap=1; - ReadLRParm(txfile); - } } } @@ -3905,8 +7585,7 @@ int main(char argc, char *argv[]) { strncpy(longley_file,argv[z],253); longley_plot=1; - /* Doing this twice is harmless */ - ReadLRParm(txfile); + pt2pt_mode=1; } } @@ -3921,6 +7600,7 @@ int main(char argc, char *argv[]) strncpy(rxfile,argv[z],253); rx_site=LoadQTH(rxfile); rxsite=1; + pt2pt_mode=1; } } @@ -3930,28 +7610,75 @@ int main(char argc, char *argv[]) z=x+1; - while (z<=y && argv[z][0] && argv[z][0]!='-' && cities<5) + while (z<=y && argv[z][0] && argv[z][0]!='-' && cities<5) + { + strncpy(city_file[cities],argv[z],253); + cities++; + z++; + } + + z--; + } + + if (strcmp(argv[x],"-b")==0) + { + /* Read Boundary File(s) */ + + z=x+1; + + while (z<=y && argv[z][0] && argv[z][0]!='-' && bfs<5) + { + strncpy(boundary_file[bfs],argv[z],253); + 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",&forced_freq); + + if (forced_freq<20.0) + forced_freq=0.0; + + if (forced_freq>20.0e3) + forced_freq=20.0e3; + } + } + + if (strcmp(argv[x],"-erp")==0) + { + z=x+1; + + if (z<=y && argv[z][0] && argv[z][0]!='-') { - strncpy(city_file[cities],argv[z],253); - cities++; - z++; - } - z--; + sscanf(argv[z],"%lf",&forced_erp); + + if (forced_erp<0.0) + forced_erp=-1.0; + } } - if (strcmp(argv[x],"-b")==0) + if (strcmp(argv[x],"-ano")==0) { - /* Read Boundary File(s) */ + z=x+1; + + if (z<=y && argv[z][0] && argv[z][0]!='-') + strncpy(ano_filename,argv[z],253); + } + if (strcmp(argv[x],"-ani")==0) + { z=x+1; - while (z<=y && argv[z][0] && argv[z][0]!='-' && bfs<5) - { - strncpy(boundary_file[bfs],argv[z],253); - bfs++; - z++; - } - z--; + if (z<=y && argv[z][0] && argv[z][0]!='-') + strncpy(ani_filename,argv[z],253); } } @@ -3981,13 +7708,34 @@ int main(char argc, char *argv[]) exit (-1); } - if ((coverage+LRmap)==0 && rx_site.lat==91.0 && rx_site.lon==361.0) + if ((coverage+LRmap+ani_filename[0])==0 && rx_site.lat==91.0 && rx_site.lon==361.0) { - fprintf(stderr,"\n%c*** ERROR: No receiver site found or specified!\n\n",7); - exit (-1); + if (max_range!=0.0 && txsites!=0) + { + /* Plot topographic map of radius "max_range" */ + + map=0; + topomap=1; + } + + else + { + fprintf(stderr,"\n%c*** ERROR: No receiver site found or specified!\n\n",7); + exit (-1); + } } - /* No errors were detected. Whew! :-) */ + /* No major errors were detected. Whew! :-) */ + + /* Adjust input parameters if -metric option is used */ + + if (metric) + { + altitudeLR/=METERS_PER_FOOT; /* meters --> feet */ + max_range/=KM_PER_MILE; /* kilometers --> miles */ + altitude/=METERS_PER_FOOT; /* meters --> feet */ + clutter/=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 @@ -3998,12 +7746,12 @@ int main(char argc, char *argv[]) if (sdf_path[0]==0) { env=getenv("HOME"); - sprintf(string,"%s/.splat_path",env); + snprintf(string,253,"%s/.splat_path",env); fd=fopen(string,"r"); if (fd!=NULL) { - fgets(string,253,fd); + s=fgets(string,253,fd); /* Remove and/or from string */ @@ -4032,6 +7780,48 @@ int main(char argc, char *argv[]) fprintf(stdout,"%s",header); fflush(stdout); + if (ani_filename[0]) + { + ReadLRParm(tx_site[0],0); /* Get ERP status */ + y=LoadANO(ani_filename); + + for (x=0; x0.0) + if (LonDiff(txlon,max_lon)>=0.0) max_lon=txlon; } @@ -4073,73 +7863,17 @@ int main(char argc, char *argv[]) if (LonDiff(rxlon,min_lon)<0.0) min_lon=rxlon; - if (LonDiff(rxlon,max_lon)>0.0) + if (LonDiff(rxlon,max_lon)>=0.0) max_lon=rxlon; } - /* Load the required SDF files */ - 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); - } - } + LoadTopoData(max_lon, min_lon, max_lat, min_lat); - if (coverage | LRmap) + if (area_mode || topomap) { - if (LRmap) - txsites=1; - - for (z=0; zdeg_limit) deg_range_lon=deg_limit; - north_min=(int)floor(tx_site[z].lat-deg_range); north_max=(int)floor(tx_site[z].lat+deg_range); @@ -4233,100 +7978,118 @@ int main(char argc, char *argv[]) if (LonDiff(west_min,min_lon)<0.0) min_lon=west_min; - if (LonDiff(west_max,max_lon)>0.0) + if (LonDiff(west_max,max_lon)>=0.0) max_lon=west_max; } + /* Load any additional SDF files, if required */ - /* Load the required SDF files */ + LoadTopoData(max_lon, min_lon, max_lat, min_lat); + } + + if (udt_file[0]) + LoadUDT(udt_file); - 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); + /***** Let the SPLATting begin! *****/ + + if (pt2pt_mode) + { + PlaceMarker(rx_site); - while (ymin<0) - ymin+=360; + if (terrain_plot) + { + /* Extract extension (if present) + from "terrain_file" */ - while (ymin>=360) - ymin-=360; + y=strlen(terrain_file); - ymax=ymin+1; + for (x=y-1; x>0 && terrain_file[x]!='.'; x--); - while (ymax<0) - ymax+=360; + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + ext[z-(x+1)]=tolower(terrain_file[z]); - while (ymax>=360) - ymax-=360; + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + terrain_file[x]=0; /* Chop off extension */ + } - sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax); - LoadSDF(string); - } + else + strncpy(ext,"png\0",4); } - else + if (elevation_plot) { - for (y=0; y<=width; y++) - for (x=min_lat; x<=max_lat; x++) - { - ymin=(int)(max_lon+(double)y); + /* Extract extension (if present) + from "elevation_file" */ - while (ymin<0) - ymin+=360; + y=strlen(elevation_file); - while (ymin>=360) - ymin-=360; - - ymax=ymin+1; + for (x=y-1; x>0 && elevation_file[x]!='.'; x--); - while (ymax<0) - ymax+=360; + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + ext[z-(x+1)]=tolower(elevation_file[z]); - while (ymax>=360) - ymax-=360; + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + elevation_file[x]=0; /* Chop off extension */ + } - sprintf(string,"%d:%d:%d:%d",x, x+1, ymin, ymax); - LoadSDF(string); - } + else + strncpy(ext,"png\0",4); } - } + if (height_plot) + { + /* Extract extension (if present) + from "height_file" */ - if (mapfile[0]) - map=1; + y=strlen(height_file); - if (coverage | LRmap) - { - for (x=0; x0 && height_file[x]!='.'; x--); - if (LRmap) - PlotLRMap(tx_site[x],altitudeLR); + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + ext[z-(x+1)]=tolower(height_file[z]); - PlaceMarker(tx_site[x]); + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + height_file[x]=0; /* Chop off extension */ + } - if (report!='N') - SiteReport(tx_site[x]); + else + strncpy(ext,"png\0",4); } - map=1; - } + if (longley_plot) + { + /* Extract extension (if present) + from "longley_file" */ - if (coverage==0 && LRmap==0) - { - PlaceMarker(rx_site); + y=strlen(longley_file); - for (x=0; x0 && longley_file[x]!='.'; x--); + + if (x>0) /* Extension found */ + { + for (z=x+1; z<=y && (z-(x+1))<10; z++) + ext[z-(x+1)]=tolower(longley_file[z]); + + ext[z-(x+1)]=0; /* Ensure an ending 0 */ + longley_file[x]=0; /* Chop off extension */ + } + + else + strncpy(ext,"png\0",4); + } + + for (x=0; x1) - { - for (x=0; terrain_file[x]!='.' && terrain_file[x]!=0 && x<80; x++); + if (txsites>1) + snprintf(string,250,"%s-%c.%s%c",longley_file,'1'+x,ext,0); + else + snprintf(string,250,"%s.%s%c",longley_file,ext,0); - if (terrain_file[x]=='.') /* extension */ + if (nositereports==0) { - ext[0]='.'; - for (y=1, z=x, x++; terrain_file[x]!=0 && x<253 && y<14; x++, y++) - ext[y]=terrain_file[x]; + if (longley_file[0]==0) + { + ReadLRParm(tx_site[x],0); + PathReport(tx_site[x],rx_site,string,0); + } - ext[y]=0; - terrain_file[z]=0; + else + { + ReadLRParm(tx_site[x],1); + PathReport(tx_site[x],rx_site,string,longley_file[0]); + } } - else + if (terrain_plot) { - ext[0]=0; /* No extension */ - terrain_file[x]=0; - } + if (txsites>1) + snprintf(string,250,"%s-%c.%s%c",terrain_file,'1'+x,ext,0); + else + snprintf(string,250,"%s.%s%c",terrain_file,ext,0); - for (count=0; count1) - { - for (x=0; elevation_file[x]!='.' && elevation_file[x]!=0 && x<80; x++); - if (elevation_file[x]=='.') /* extension */ + if (elevation_plot) { - ext[0]='.'; - for (y=1, z=x, x++; elevation_file[x]!=0 && x<253 && y<14; x++, y++) - ext[y]=elevation_file[x]; + if (txsites>1) + snprintf(string,250,"%s-%c.%s%c",elevation_file,'1'+x,ext,0); + else + snprintf(string,250,"%s.%s%c",elevation_file,ext,0); - ext[y]=0; - elevation_file[z]=0; + GraphElevation(tx_site[x],rx_site,string); } - else + if (height_plot) { - ext[0]=0; /* No extension */ - elevation_file[x]=0; - } + if (txsites>1) + snprintf(string,250,"%s-%c.%s%c",height_file,'1'+x,ext,0); + else + snprintf(string,250,"%s.%s%c",height_file,ext,0); - for (count=0; count1) + for (x=0; x1) + /* Label the map */ + + if (kml==0) { - for (x=0; longley_file[x]!='.' && longley_file[x]!=0 && x<80; x++); + for (x=0; x