forked from ExternalVendorCode/Signal-Server
1999 lines
47 KiB
C++
1999 lines
47 KiB
C++
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
|
|
#include "common.h"
|
|
#include "main.hh"
|
|
#include "inputs.hh"
|
|
#include "models/cost.hh"
|
|
#include "models/ecc33.hh"
|
|
#include "models/ericsson.hh"
|
|
#include "models/fspl.hh"
|
|
#include "models/hata.hh"
|
|
#include "models/itwom3.0.hh"
|
|
#include "models/sui.hh"
|
|
|
|
void DoPathLoss(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[][]
|
|
array (only). 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];
|
|
unsigned red, green, blue, terrain = 0;
|
|
unsigned char found, mask, cityorcounty;
|
|
int indx, x, y, z, x0 = 0, y0 = 0, loss, match;
|
|
double lat, lon, conversion, one_over_gamma, minwest;
|
|
FILE *fd;
|
|
|
|
one_over_gamma = 1.0 / GAMMA;
|
|
conversion =
|
|
255.0 / pow((double)(max_elevation - min_elevation),
|
|
one_over_gamma);
|
|
|
|
LoadLossColors(xmtr[0]);
|
|
|
|
if (filename[0] == 0) {
|
|
strncpy(filename, xmtr[0].filename, 254);
|
|
filename[strlen(filename) - 4] = 0; /* Remove .qth */
|
|
}
|
|
|
|
y = strlen(filename);
|
|
|
|
if (y > 4) {
|
|
if (filename[y - 1] == 'm' && filename[y - 2] == 'p'
|
|
&& filename[y - 3] == 'p' && filename[y - 4] == '.')
|
|
y -= 4;
|
|
}
|
|
|
|
for (x = 0; x < y; x++) {
|
|
mapfile[x] = filename[x];
|
|
}
|
|
|
|
mapfile[x] = '.';
|
|
mapfile[x + 1] = 'p';
|
|
mapfile[x + 2] = 'p';
|
|
mapfile[x + 3] = 'm';
|
|
mapfile[x + 4] = 0;
|
|
|
|
minwest = ((double)min_west) + dpp;
|
|
|
|
if (minwest > 360.0)
|
|
minwest -= 360.0;
|
|
|
|
north = (double)max_north - dpp;
|
|
|
|
if (kml || geo)
|
|
south = (double)min_north; /* No bottom legend */
|
|
else
|
|
south = (double)min_north - (30.0 / ppd); /* 30 pixels for bottom legend */
|
|
|
|
east = (minwest < 180.0 ? -minwest : 360.0 - min_west);
|
|
west = (double)(max_west < 180 ? -max_west : 360 - max_west);
|
|
|
|
fd = fopen(mapfile, "wb");
|
|
|
|
fprintf(fd, "P6\n%u %u\n255\n", width, (kml ? height : height + 30));
|
|
if (debug) {
|
|
fprintf(stdout, "\nWriting \"%s\" (%ux%u pixmap image)... ",
|
|
mapfile, width, (kml ? height : height + 30));
|
|
fflush(stdout);
|
|
}
|
|
for (y = 0, lat = north; y < (int)height;
|
|
y++, lat = north - (dpp * (double)y)) {
|
|
for (x = 0, lon = max_west; x < (int)width;
|
|
x++, lon = max_west - (dpp * (double)x)) {
|
|
if (lon < 0.0)
|
|
lon += 360.0;
|
|
|
|
for (indx = 0, found = 0;
|
|
indx < MAXPAGES && found == 0;) {
|
|
x0 = (int)rint(ppd *
|
|
(lat -
|
|
(double)dem[indx].min_north));
|
|
y0 = mpi -
|
|
(int)rint(ppd *
|
|
(LonDiff
|
|
((double)dem[indx].max_west,
|
|
lon)));
|
|
// fix for multi-tile lidar
|
|
if(width==10000 && (indx==1 || indx==3)){
|
|
if(y0 >= 3432){ //3535
|
|
y0=y0-3432;
|
|
}
|
|
}
|
|
|
|
|
|
if (x0 >= 0 && x0 <= mpi && y0 >= 0
|
|
&& y0 <= mpi)
|
|
found = 1;
|
|
else
|
|
indx++;
|
|
}
|
|
|
|
if (found) {
|
|
mask = dem[indx].mask[x0][y0];
|
|
loss = (dem[indx].signal[x0][y0]);
|
|
cityorcounty = 0;
|
|
|
|
match = 255;
|
|
|
|
red = 0;
|
|
green = 0;
|
|
blue = 0;
|
|
|
|
if (loss <= region.level[0])
|
|
match = 0;
|
|
else {
|
|
for (z = 1;
|
|
(z < region.levels
|
|
&& match == 255); z++) {
|
|
if (loss >= region.level[z - 1]
|
|
&& loss < region.level[z])
|
|
match = z;
|
|
}
|
|
}
|
|
|
|
if (match < region.levels) {
|
|
red = region.color[match][0];
|
|
green = region.color[match][1];
|
|
blue = region.color[match][2];
|
|
}
|
|
|
|
if (mask & 2) {
|
|
/* Text Labels: Red or otherwise */
|
|
|
|
if (red >= 180 && green <= 75
|
|
&& blue <= 75 && loss == 0)
|
|
fprintf(fd, "%c%c%c", 255 ^ red,
|
|
255 ^ green,
|
|
255 ^ blue);
|
|
else
|
|
fprintf(fd, "%c%c%c", 255, 0,
|
|
0);
|
|
|
|
cityorcounty = 1;
|
|
}
|
|
|
|
else if (mask & 4) {
|
|
/* County Boundaries: Black */
|
|
|
|
fprintf(fd, "%c%c%c", 0, 0, 0);
|
|
|
|
cityorcounty = 1;
|
|
}
|
|
|
|
if (cityorcounty == 0) {
|
|
if (loss == 0
|
|
|| (contour_threshold != 0
|
|
&& loss >
|
|
abs(contour_threshold))) {
|
|
if (ngs) /* No terrain */
|
|
fprintf(fd, "%c%c%c",
|
|
255, 255, 255);
|
|
else {
|
|
/* Display land or sea elevation */
|
|
|
|
if (dem[indx].
|
|
data[x0][y0] == 0)
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
0, 0,
|
|
170);
|
|
else {
|
|
terrain =
|
|
(unsigned)
|
|
(0.5 +
|
|
pow((double)(dem[indx].data[x0][y0] - min_elevation), one_over_gamma) * conversion);
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
terrain,
|
|
terrain,
|
|
terrain);
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
/* Plot path loss in color */
|
|
|
|
if (red != 0 || green != 0
|
|
|| blue != 0)
|
|
fprintf(fd, "%c%c%c",
|
|
red, green,
|
|
blue);
|
|
|
|
else { /* terrain / sea-level */
|
|
|
|
if (dem[indx].
|
|
data[x0][y0] == 0)
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
0, 0,
|
|
170);
|
|
else {
|
|
/* Elevation: Greyscale */
|
|
terrain =
|
|
(unsigned)
|
|
(0.5 +
|
|
pow((double)(dem[indx].data[x0][y0] - min_elevation), one_over_gamma) * conversion);
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
terrain,
|
|
terrain,
|
|
terrain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
/* We should never get here, but if */
|
|
/* we do, display the region as black */
|
|
|
|
fprintf(fd, "%c%c%c", 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fd);
|
|
|
|
}
|
|
|
|
void DoSigStr(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];
|
|
unsigned terrain, red, green, blue;
|
|
unsigned char found, mask, cityorcounty;
|
|
int indx, x, y, z = 1, x0 = 0, y0 = 0, signal, match;
|
|
double conversion, one_over_gamma, lat, lon, minwest;
|
|
FILE *fd;
|
|
|
|
one_over_gamma = 1.0 / GAMMA;
|
|
conversion =
|
|
255.0 / pow((double)(max_elevation - min_elevation),
|
|
one_over_gamma);
|
|
|
|
LoadSignalColors(xmtr[0]);
|
|
|
|
if (filename[0] == 0) {
|
|
strncpy(filename, xmtr[0].filename, 254);
|
|
filename[strlen(filename) - 4] = 0; /* Remove .qth */
|
|
}
|
|
|
|
y = strlen(filename);
|
|
|
|
if (y > 4) {
|
|
if (filename[y - 1] == 'm' && filename[y - 2] == 'p'
|
|
&& filename[y - 3] == 'p' && filename[y - 4] == '.')
|
|
y -= 4;
|
|
}
|
|
|
|
for (x = 0; x < y; x++) {
|
|
mapfile[x] = filename[x];
|
|
}
|
|
|
|
mapfile[x] = '.';
|
|
mapfile[x + 1] = 'p';
|
|
mapfile[x + 2] = 'p';
|
|
mapfile[x + 3] = 'm';
|
|
mapfile[x + 4] = 0;
|
|
|
|
minwest = ((double)min_west) + dpp;
|
|
|
|
if (minwest > 360.0)
|
|
minwest -= 360.0;
|
|
|
|
north = (double)max_north - dpp;
|
|
|
|
south = (double)min_north; /* No bottom legend */
|
|
|
|
east = (minwest < 180.0 ? -minwest : 360.0 - min_west);
|
|
west = (double)(max_west < 180 ? -max_west : 360 - max_west);
|
|
fd = fopen(mapfile, "wb");
|
|
|
|
fprintf(fd, "P6\n%u %u\n255\n", width, (kml ? height : height + 30));
|
|
if (debug) {
|
|
fprintf(stdout, "\nWriting \"%s\" (%ux%u pixmap image)... ",
|
|
mapfile, width, (kml ? height : height + 30));
|
|
fflush(stdout);
|
|
}
|
|
for (y = 0, lat = north; y < (int)height;
|
|
y++, lat = north - (dpp * (double)y)) {
|
|
for (x = 0, lon = max_west; x < (int)width;
|
|
x++, lon = max_west - (dpp * (double)x)) {
|
|
if (lon < 0.0)
|
|
lon += 360.0;
|
|
|
|
for (indx = 0, found = 0;
|
|
indx < MAXPAGES && found == 0;) {
|
|
x0 = (int)rint(ppd *
|
|
(lat -
|
|
(double)dem[indx].min_north));
|
|
y0 = mpi -
|
|
(int)rint(ppd *
|
|
(LonDiff
|
|
((double)dem[indx].max_west,
|
|
lon)));
|
|
|
|
// fix for multi-tile lidar
|
|
if(width==10000 && (indx==1 || indx==3)){
|
|
if(y0 >= 3432){ //3535
|
|
y0=y0-3432;
|
|
}
|
|
}
|
|
|
|
|
|
if (x0 >= 0 && x0 <= mpi && y0 >= 0
|
|
&& y0 <= mpi)
|
|
found = 1;
|
|
else
|
|
indx++;
|
|
}
|
|
|
|
if (found) {
|
|
mask = dem[indx].mask[x0][y0];
|
|
signal = (dem[indx].signal[x0][y0]) - 100;
|
|
cityorcounty = 0;
|
|
match = 255;
|
|
|
|
red = 0;
|
|
green = 0;
|
|
blue = 0;
|
|
|
|
if (signal >= region.level[0])
|
|
match = 0;
|
|
else {
|
|
for (z = 1;
|
|
(z < region.levels
|
|
&& match == 255); z++) {
|
|
if (signal < region.level[z - 1]
|
|
&& signal >=
|
|
region.level[z])
|
|
match = z;
|
|
}
|
|
}
|
|
|
|
if (match < region.levels) {
|
|
red = region.color[match][0];
|
|
green = region.color[match][1];
|
|
blue = region.color[match][2];
|
|
}
|
|
|
|
if (mask & 2) {
|
|
/* Text Labels: Red or otherwise */
|
|
|
|
if (red >= 180 && green <= 75
|
|
&& blue <= 75)
|
|
fprintf(fd, "%c%c%c", 255 ^ red,
|
|
255 ^ green,
|
|
255 ^ blue);
|
|
else
|
|
fprintf(fd, "%c%c%c", 255, 0,
|
|
0);
|
|
|
|
cityorcounty = 1;
|
|
}
|
|
|
|
else if (mask & 4) {
|
|
/* County Boundaries: Black */
|
|
|
|
fprintf(fd, "%c%c%c", 0, 0, 0);
|
|
|
|
cityorcounty = 1;
|
|
}
|
|
|
|
if (cityorcounty == 0) {
|
|
if (contour_threshold != 0
|
|
&& signal < contour_threshold) {
|
|
if (ngs)
|
|
fprintf(fd, "%c%c%c",
|
|
255, 255, 255);
|
|
else {
|
|
/* Display land or sea elevation */
|
|
|
|
if (dem[indx].
|
|
data[x0][y0] == 0)
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
0, 0,
|
|
170);
|
|
else {
|
|
terrain =
|
|
(unsigned)
|
|
(0.5 +
|
|
pow((double)(dem[indx].data[x0][y0] - min_elevation), one_over_gamma) * conversion);
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
terrain,
|
|
terrain,
|
|
terrain);
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
/* Plot field strength regions in color */
|
|
|
|
if (red != 0 || green != 0
|
|
|| blue != 0)
|
|
fprintf(fd, "%c%c%c",
|
|
red, green,
|
|
blue);
|
|
|
|
else { /* terrain / sea-level */
|
|
|
|
if (ngs)
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
255,
|
|
255,
|
|
255);
|
|
else {
|
|
if (dem[indx].
|
|
data[x0][y0]
|
|
== 0)
|
|
fprintf
|
|
(fd,
|
|
"%c%c%c",
|
|
0,
|
|
0,
|
|
170);
|
|
else {
|
|
/* Elevation: Greyscale */
|
|
terrain
|
|
=
|
|
(unsigned)
|
|
(0.5
|
|
+
|
|
pow
|
|
((double)(dem[indx].data[x0][y0] - min_elevation), one_over_gamma) * conversion);
|
|
fprintf
|
|
(fd,
|
|
"%c%c%c",
|
|
terrain,
|
|
terrain,
|
|
terrain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
/* We should never get here, but if */
|
|
/* we do, display the region as black */
|
|
|
|
fprintf(fd, "%c%c%c", 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fd);
|
|
|
|
}
|
|
|
|
void DoRxdPwr(char *filename, unsigned char geo, unsigned char kml,
|
|
unsigned char ngs, struct site *xmtr, unsigned char txsites)
|
|
{
|
|
/* This function generates a topographic map in Portable Pix Map
|
|
(PPM) format based on the signal power level values held in the
|
|
signal[][] array. The image created is rotated counter-clockwise
|
|
90 degrees from its representation in dem[][] so that north
|
|
points up and east points right in the image generated. */
|
|
|
|
char mapfile[255];
|
|
unsigned terrain, red, green, blue;
|
|
unsigned char found, mask, cityorcounty;
|
|
int indx, x, y, z = 1, x0 = 0, y0 = 0, dBm, match;
|
|
double conversion, one_over_gamma, lat, lon, minwest;
|
|
FILE *fd;
|
|
|
|
one_over_gamma = 1.0 / GAMMA;
|
|
conversion =
|
|
255.0 / pow((double)(max_elevation - min_elevation),
|
|
one_over_gamma);
|
|
|
|
LoadDBMColors(xmtr[0]);
|
|
|
|
if (filename[0] == 0) {
|
|
strncpy(filename, xmtr[0].filename, 254);
|
|
filename[strlen(filename) - 4] = 0; /* Remove .qth */
|
|
}
|
|
|
|
y = strlen(filename);
|
|
|
|
if (y > 4) {
|
|
if (filename[y - 1] == 'm' && filename[y - 2] == 'p'
|
|
&& filename[y - 3] == 'p' && filename[y - 4] == '.')
|
|
y -= 4;
|
|
}
|
|
|
|
for (x = 0; x < y; x++) {
|
|
mapfile[x] = filename[x];
|
|
}
|
|
|
|
mapfile[x] = '.';
|
|
mapfile[x + 1] = 'p';
|
|
mapfile[x + 2] = 'p';
|
|
mapfile[x + 3] = 'm';
|
|
mapfile[x + 4] = 0;
|
|
|
|
minwest = ((double)min_west) + dpp;
|
|
|
|
if (minwest > 360.0)
|
|
minwest -= 360.0;
|
|
|
|
north = (double)max_north - dpp;
|
|
|
|
south = (double)min_north; /* No bottom legend */
|
|
|
|
east = (minwest < 180.0 ? -minwest : 360.0 - min_west);
|
|
west = (double)(max_west < 180 ? -max_west : 360 - max_west);
|
|
|
|
fd = fopen(mapfile, "wb");
|
|
|
|
fprintf(fd, "P6\n%u %u\n255\n", width, (kml ? height : height));
|
|
if (debug) {
|
|
fprintf(stdout, "\nWriting \"%s\" (%ux%u pixmap image)...\n",
|
|
mapfile, width, (kml ? height : height));
|
|
fflush(stdout);
|
|
}
|
|
|
|
// Draw image of x by y pixels
|
|
for (y = 0, lat = north; y < (int)height;
|
|
y++, lat = north - (dpp * (double)y)) {
|
|
for (x = 0, lon = max_west; x < (int)width;
|
|
x++, lon = max_west - (dpp * (double)x)) {
|
|
if (lon < 0.0)
|
|
lon += 360.0;
|
|
|
|
for (indx = 0, found = 0;
|
|
indx < MAXPAGES && found == 0;) {
|
|
|
|
x0 = (int)rint((ppd *
|
|
(lat -
|
|
(double)dem[indx].min_north)));
|
|
y0 = mpi -
|
|
(int)rint(ppd *
|
|
(LonDiff
|
|
((double)dem[indx].max_west,lon)));
|
|
|
|
|
|
if (x0 >= 0 && x0 <= mpi && y0 >= 0
|
|
&& y0 <= mpi)
|
|
found = 1;
|
|
else
|
|
indx++;
|
|
|
|
|
|
}
|
|
|
|
if (found) {
|
|
mask = dem[indx].mask[x0][y0];
|
|
dBm = (dem[indx].signal[x0][y0]) - 200;
|
|
cityorcounty = 0;
|
|
match = 255;
|
|
|
|
red = 0;
|
|
green = 0;
|
|
blue = 0;
|
|
|
|
if (dBm >= region.level[0])
|
|
match = 0;
|
|
else {
|
|
for (z = 1;
|
|
(z < region.levels
|
|
&& match == 255); z++) {
|
|
if (dBm < region.level[z - 1]
|
|
&& dBm >= region.level[z])
|
|
match = z;
|
|
}
|
|
}
|
|
|
|
if (match < region.levels) {
|
|
red = region.color[match][0];
|
|
green = region.color[match][1];
|
|
blue = region.color[match][2];
|
|
}
|
|
|
|
if (mask & 2) {
|
|
/* Text Labels: Red or otherwise */
|
|
|
|
if (red >= 180 && green <= 75
|
|
&& blue <= 75 && dBm != 0)
|
|
fprintf(fd, "%c%c%c", 255 ^ red,
|
|
255 ^ green,
|
|
255 ^ blue);
|
|
else
|
|
fprintf(fd, "%c%c%c", 255, 0,
|
|
0);
|
|
|
|
cityorcounty = 1;
|
|
}
|
|
|
|
else if (mask & 4) {
|
|
/* County Boundaries: Black */
|
|
fprintf(fd, "%c%c%c", 0, 0, 0);
|
|
cityorcounty = 1;
|
|
}
|
|
|
|
if (cityorcounty == 0) {
|
|
if (contour_threshold != 0
|
|
&& dBm < contour_threshold) {
|
|
if (ngs) /* No terrain */
|
|
fprintf(fd, "%c%c%c",
|
|
255, 255, 255);
|
|
else {
|
|
/* Display land or sea elevation */
|
|
|
|
if (dem[indx].
|
|
data[x0][y0] == 0)
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
0, 0,
|
|
170);
|
|
else {
|
|
terrain =
|
|
(unsigned)
|
|
(0.5 +
|
|
pow((double)(dem[indx].data[x0][y0] - min_elevation), one_over_gamma) * conversion);
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
terrain,
|
|
terrain,
|
|
terrain);
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
/* Plot signal power level regions in color */
|
|
|
|
if (red != 0 || green != 0
|
|
|| blue != 0)
|
|
fprintf(fd, "%c%c%c",
|
|
red, green,
|
|
blue);
|
|
|
|
else { /* terrain / sea-level */
|
|
|
|
if (ngs)
|
|
fprintf(fd,
|
|
"%c%c%c",
|
|
255,
|
|
255,
|
|
255); // WHITE
|
|
else {
|
|
if (dem[indx].
|
|
data[x0][y0]
|
|
== 0)
|
|
fprintf
|
|
(fd,
|
|
"%c%c%c",
|
|
0,
|
|
0,
|
|
170); // BLUE
|
|
else {
|
|
/* Elevation: Greyscale */
|
|
terrain
|
|
=
|
|
(unsigned)
|
|
(0.5
|
|
+
|
|
pow
|
|
((double)(dem[indx].data[x0][y0] - min_elevation), one_over_gamma) * conversion);
|
|
fprintf
|
|
(fd,
|
|
"%c%c%c",
|
|
terrain,
|
|
terrain,
|
|
terrain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
/* We should never get here, but if */
|
|
/* we do, display the region as black */
|
|
|
|
fprintf(fd, "%c%c%c", 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fclose(fd);
|
|
|
|
}
|
|
|
|
void DoLOS(char *filename, unsigned char geo, unsigned char kml,
|
|
unsigned char ngs, struct site *xmtr, unsigned char txsites)
|
|
{
|
|
/* This function generates a topographic map in Portable Pix Map
|
|
(PPM) format based on the signal power level values held in the
|
|
signal[][] array. The image created is rotated counter-clockwise
|
|
90 degrees from its representation in dem[][] so that north
|
|
points up and east points right in the image generated. */
|
|
|
|
char mapfile[255];
|
|
unsigned terrain;
|
|
unsigned char found, mask;
|
|
int indx, x, y, x0 = 0, y0 = 0;
|
|
double conversion, one_over_gamma, lat, lon, minwest;
|
|
FILE *fd;
|
|
|
|
one_over_gamma = 1.0 / GAMMA;
|
|
conversion =
|
|
255.0 / pow((double)(max_elevation - min_elevation),
|
|
one_over_gamma);
|
|
|
|
if (filename[0] == 0) {
|
|
strncpy(filename, xmtr[0].filename, 254);
|
|
filename[strlen(filename) - 4] = 0; /* Remove .qth */
|
|
}
|
|
|
|
y = strlen(filename);
|
|
|
|
if (y > 4) {
|
|
if (filename[y - 1] == 'm' && filename[y - 2] == 'p'
|
|
&& filename[y - 3] == 'p' && filename[y - 4] == '.')
|
|
y -= 4;
|
|
}
|
|
|
|
for (x = 0; x < y; x++) {
|
|
mapfile[x] = filename[x];
|
|
}
|
|
|
|
mapfile[x] = '.';
|
|
mapfile[x + 1] = 'p';
|
|
mapfile[x + 2] = 'p';
|
|
mapfile[x + 3] = 'm';
|
|
mapfile[x + 4] = 0;
|
|
|
|
minwest = ((double)min_west) + dpp;
|
|
|
|
if (minwest > 360.0)
|
|
minwest -= 360.0;
|
|
|
|
north = (double)max_north - dpp;
|
|
|
|
south = (double)min_north; /* No bottom legend */
|
|
|
|
east = (minwest < 180.0 ? -minwest : 360.0 - min_west);
|
|
west = (double)(max_west < 180 ? -max_west : 360 - max_west);
|
|
|
|
fd = fopen(mapfile, "wb");
|
|
|
|
fprintf(fd, "P6\n%u %u\n255\n", width, (kml ? height : height + 30));
|
|
if (debug) {
|
|
fprintf(stdout, "\nWriting \"%s\" (%ux%u pixmap image)... ",
|
|
mapfile, width, (kml ? height : height + 30));
|
|
fflush(stdout);
|
|
}
|
|
|
|
for (y = 0, lat = north; y < (int)height;
|
|
y++, lat = north - (dpp * (double)y)) {
|
|
for (x = 0, lon = max_west; x < (int)width;
|
|
x++, lon = max_west - (dpp * (double)x)) {
|
|
if (lon < 0.0)
|
|
lon += 360.0;
|
|
|
|
for (indx = 0, found = 0;
|
|
indx < MAXPAGES && found == 0;) {
|
|
x0 = (int)rint(ppd *
|
|
(lat -
|
|
(double)dem[indx].min_north));
|
|
y0 = mpi -
|
|
(int)rint(ppd *
|
|
(LonDiff
|
|
((double)dem[indx].max_west,
|
|
lon)));
|
|
|
|
if (x0 >= 0 && x0 <= mpi && y0 >= 0
|
|
&& y0 <= mpi)
|
|
found = 1;
|
|
else
|
|
indx++;
|
|
}
|
|
|
|
if (found) {
|
|
mask = dem[indx].mask[x0][y0];
|
|
|
|
if (mask & 2)
|
|
/* Text Labels: Red */
|
|
fprintf(fd, "%c%c%c", 255, 0, 0);
|
|
|
|
else if (mask & 4)
|
|
/* County Boundaries: Light Cyan */
|
|
fprintf(fd, "%c%c%c", 128, 128, 255);
|
|
|
|
else
|
|
switch (mask & 57) {
|
|
case 1:
|
|
/* TX1: Green */
|
|
fprintf(fd, "%c%c%c", 0, 255,
|
|
0);
|
|
break;
|
|
|
|
case 8:
|
|
/* TX2: Cyan */
|
|
fprintf(fd, "%c%c%c", 0, 255,
|
|
255);
|
|
break;
|
|
|
|
case 9:
|
|
/* TX1 + TX2: Yellow */
|
|
fprintf(fd, "%c%c%c", 255, 255,
|
|
0);
|
|
break;
|
|
|
|
case 16:
|
|
/* TX3: Medium Violet */
|
|
fprintf(fd, "%c%c%c", 147, 112,
|
|
219);
|
|
break;
|
|
|
|
case 17:
|
|
/* TX1 + TX3: Pink */
|
|
fprintf(fd, "%c%c%c", 255, 192,
|
|
203);
|
|
break;
|
|
|
|
case 24:
|
|
/* TX2 + TX3: Orange */
|
|
fprintf(fd, "%c%c%c", 255, 165,
|
|
0);
|
|
break;
|
|
|
|
case 25:
|
|
/* TX1 + TX2 + TX3: Dark Green */
|
|
fprintf(fd, "%c%c%c", 0, 100,
|
|
0);
|
|
break;
|
|
|
|
case 32:
|
|
/* TX4: Sienna 1 */
|
|
fprintf(fd, "%c%c%c", 255, 130,
|
|
71);
|
|
break;
|
|
|
|
case 33:
|
|
/* TX1 + TX4: Green Yellow */
|
|
fprintf(fd, "%c%c%c", 173, 255,
|
|
47);
|
|
break;
|
|
|
|
case 40:
|
|
/* TX2 + TX4: Dark Sea Green 1 */
|
|
fprintf(fd, "%c%c%c", 193, 255,
|
|
193);
|
|
break;
|
|
|
|
case 41:
|
|
/* TX1 + TX2 + TX4: Blanched Almond */
|
|
fprintf(fd, "%c%c%c", 255, 235,
|
|
205);
|
|
break;
|
|
|
|
case 48:
|
|
/* TX3 + TX4: Dark Turquoise */
|
|
fprintf(fd, "%c%c%c", 0, 206,
|
|
209);
|
|
break;
|
|
|
|
case 49:
|
|
/* TX1 + TX3 + TX4: Medium Spring Green */
|
|
fprintf(fd, "%c%c%c", 0, 250,
|
|
154);
|
|
break;
|
|
|
|
case 56:
|
|
/* TX2 + TX3 + TX4: Tan */
|
|
fprintf(fd, "%c%c%c", 210, 180,
|
|
140);
|
|
break;
|
|
|
|
case 57:
|
|
/* TX1 + TX2 + TX3 + TX4: Gold2 */
|
|
fprintf(fd, "%c%c%c", 238, 201,
|
|
0);
|
|
break;
|
|
|
|
default:
|
|
if (ngs) /* No terrain */
|
|
fprintf(fd, "%c%c%c",
|
|
255, 255, 255);
|
|
else {
|
|
/* 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
/* We should never get here, but if */
|
|
/* we do, display the region as black */
|
|
|
|
fprintf(fd, "%c%c%c", 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fd);
|
|
|
|
}
|
|
|
|
void PathReport(struct site source, struct site destination, char *name,
|
|
char graph_it, int propmodel, int pmenv, double rxGain)
|
|
{
|
|
/* This function writes a PPA 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, 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,
|
|
free_space_loss = 0.0, eirp =
|
|
0.0, voltage, rxp, power_density, dkm, txelev;
|
|
FILE *fd = NULL, *fd2 = NULL;
|
|
|
|
snprintf(report_name, 80, "%s.txt%c", name, 0);
|
|
four_thirds_earth = FOUR_THIRDS * EARTHRADIUS;
|
|
|
|
fd2 = fopen(report_name, "w");
|
|
|
|
fprintf(fd2, "\n\t\t--==[ Path Profile Analysis ]==--\n\n");
|
|
fprintf(fd2, "Transmitter site: %s\n", source.name);
|
|
|
|
if (source.lat >= 0.0) {
|
|
|
|
if (source.lon <= 180){
|
|
fprintf(fd2, "Site location: %.4f, -%.4f\n",source.lat, source.lon);
|
|
}else{
|
|
fprintf(fd2, "Site location: %.4f, %.4f\n",source.lat, 360 - source.lon);
|
|
}
|
|
}
|
|
|
|
else {
|
|
|
|
if (source.lon <= 180){
|
|
fprintf(fd2, "Site location: %.4f, -%.4f\n",source.lat, source.lon);
|
|
}else{
|
|
fprintf(fd2, "Site location: %.4f, %.4f\n",source.lat, 360 - 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));
|
|
}
|
|
|
|
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\n");
|
|
else
|
|
fprintf(fd2, "Elevation\n");
|
|
}
|
|
|
|
/* Receiver */
|
|
|
|
fprintf(fd2, "\nReceiver site: %s\n", destination.name);
|
|
|
|
if (destination.lat >= 0.0) {
|
|
|
|
if (destination.lon <= 180){
|
|
fprintf(fd2, "Site location: %.4f, -%.4f\n",destination.lat, destination.lon);
|
|
}else{
|
|
fprintf(fd2, "Site location: %.4f, %.4f\n",destination.lat, 360 - destination.lon);
|
|
}
|
|
}
|
|
|
|
else {
|
|
|
|
if (destination.lon <= 180){
|
|
fprintf(fd2, "Site location: %.4f, -%.4f\n",destination.lat, destination.lon);
|
|
}else{
|
|
fprintf(fd2, "Site location: %.4f, %.4f\n",destination.lat, 360 - 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));
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
if (LR.frq_mhz > 0.0) {
|
|
fprintf(fd2, "\n\nPropagation model: ");
|
|
|
|
switch (propmodel) {
|
|
case 1:
|
|
fprintf(fd2, "Irregular Terrain Model\n");
|
|
break;
|
|
case 2:
|
|
fprintf(fd2, "Line of sight\n");
|
|
break;
|
|
case 3:
|
|
fprintf(fd2, "Okumura-Hata\n");
|
|
break;
|
|
case 4:
|
|
fprintf(fd2, "ECC33 (ITU-R P.529)\n");
|
|
break;
|
|
case 5:
|
|
fprintf(fd2, "Stanford University Interim\n");
|
|
break;
|
|
case 6:
|
|
fprintf(fd2, "COST231-Hata\n");
|
|
break;
|
|
case 7:
|
|
fprintf(fd2, "Free space path loss (ITU-R.525)\n");
|
|
break;
|
|
case 8:
|
|
fprintf(fd2, "ITWOM 3.0\n");
|
|
break;
|
|
case 9:
|
|
fprintf(fd2, "Ericsson\n");
|
|
break;
|
|
}
|
|
|
|
fprintf(fd2, "Model sub-type: ");
|
|
|
|
switch (pmenv) {
|
|
case 1:
|
|
fprintf(fd2, "City / Conservative\n");
|
|
break;
|
|
case 2:
|
|
fprintf(fd2, "Suburban / Average\n");
|
|
break;
|
|
case 3:
|
|
fprintf(fd2, "Rural / Optimistic\n");
|
|
break;
|
|
}
|
|
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, "Maritime Temperate, Over Land");
|
|
break;
|
|
|
|
case 7:
|
|
fprintf(fd2, "Maritime Temperate, Over Sea");
|
|
break;
|
|
|
|
default:
|
|
fprintf(fd2, "Unknown");
|
|
}
|
|
|
|
fprintf(fd2, ")\nPolarisation: %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, "\nReceiver gain: %.2f dBd\n", rxGain);
|
|
fprintf(fd2, "Receiver gain: %.2f dBi\n", rxGain+2.14);
|
|
fprintf(fd2, "Transmitter ERP plus Receiver gain: ");
|
|
|
|
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);
|
|
fprintf(fd2, "Transmitter ERP minus Receiver gain: %.2f dBm\n", dBm-rxGain);
|
|
|
|
/* EIRP = ERP + 2.14 dB */
|
|
|
|
fprintf(fd2, "Transmitter EIRP plus Receiver gain: ");
|
|
|
|
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);
|
|
|
|
if (eirp >= 10.0e3)
|
|
fprintf(fd2, "%.3lf kilowatts", eirp / 1.0e3);
|
|
|
|
dBm = 10.0 * (log10(eirp * 1000.0));
|
|
fprintf(fd2, " (%+.2f dBm)\n", dBm);
|
|
|
|
// Rx gain
|
|
fprintf(fd2, "Transmitter EIRP minus Receiver gain: %.2f dBm\n", dBm-rxGain);
|
|
}
|
|
|
|
fprintf(fd2, "\nSummary for the link between %s and %s:\n\n",
|
|
source.name, destination.name);
|
|
|
|
if (patterndB != 0.0)
|
|
fprintf(fd2,
|
|
"%s antenna pattern towards %s: %.3f (%.2f dB)\n",
|
|
source.name, destination.name, pattern,
|
|
patterndB);
|
|
|
|
ReadPath(source, destination); /* source=TX, destination=RX */
|
|
|
|
/* Copy elevations plus clutter along
|
|
path into the elev[] array. */
|
|
|
|
for (x = 1; x < path.length - 1; x++)
|
|
elev[x + 2] =
|
|
METERS_PER_FOOT * (path.elevation[x] ==
|
|
0.0 ? path.
|
|
elevation[x] : (clutter +
|
|
path.
|
|
elevation[x]));
|
|
|
|
/* Copy ending points without clutter */
|
|
|
|
elev[2] = path.elevation[0] * METERS_PER_FOOT;
|
|
elev[path.length + 1] =
|
|
path.elevation[path.length - 1] * METERS_PER_FOOT;
|
|
|
|
azimuth = rint(Azimuth(source, destination));
|
|
|
|
for (y = 2; y < (path.length - 1); y++) { /* path.length-1 avoids LR error */
|
|
distance = 5280.0 * path.distance[y];
|
|
source_alt =
|
|
four_thirds_earth + source.alt + path.elevation[0];
|
|
dest_alt =
|
|
four_thirds_earth + destination.alt +
|
|
path.elevation[y];
|
|
dest_alt2 = dest_alt * dest_alt;
|
|
source_alt2 = source_alt * source_alt;
|
|
|
|
/* Calculate the cosine of the elevation of
|
|
the receiver as seen by the transmitter. */
|
|
|
|
cos_xmtr_angle =
|
|
((source_alt2) + (distance * distance) -
|
|
(dest_alt2)) / (2.0 * source_alt * distance);
|
|
|
|
if (got_elevation_pattern) {
|
|
/* If an antenna elevation pattern is available, the
|
|
following code determines the elevation angle to
|
|
the first obstruction along the path. */
|
|
|
|
for (x = 2, block = 0; x < y && block == 0; x++) {
|
|
distance =
|
|
5280.0 * (path.distance[y] -
|
|
path.distance[x]);
|
|
test_alt =
|
|
four_thirds_earth +
|
|
path.elevation[x];
|
|
|
|
/* Calculate the cosine of the elevation
|
|
angle of the terrain (test point)
|
|
as seen by the transmitter. */
|
|
|
|
cos_test_angle =
|
|
((source_alt2) +
|
|
(distance * distance) -
|
|
(test_alt * test_alt)) / (2.0 *
|
|
source_alt
|
|
*
|
|
distance);
|
|
|
|
/* Compare these two angles to determine if
|
|
an obstruction exists. Since we're comparing
|
|
the cosines of these angles rather than
|
|
the angles themselves, the sense of the
|
|
following "if" statement is reversed from
|
|
what it would be if the angles themselves
|
|
were compared. */
|
|
|
|
if (cos_xmtr_angle >= cos_test_angle)
|
|
block = 1;
|
|
}
|
|
|
|
/* At this point, we have the elevation angle
|
|
to the first obstruction (if it exists). */
|
|
}
|
|
|
|
/* Determine path loss for each point along the
|
|
path using Longley-Rice's point_to_point mode
|
|
starting at x=2 (number_of_points = 1), the
|
|
shortest distance terrain can play a role in
|
|
path loss. */
|
|
|
|
elev[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);
|
|
*/
|
|
dkm = (elev[1] * elev[0]) / 1000; // km
|
|
txelev = elev[2] + (source.alt * METERS_PER_FOOT);
|
|
|
|
switch (propmodel) {
|
|
case 1:
|
|
// Longley Rice ITM
|
|
point_to_point_ITM(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);
|
|
break;
|
|
case 3:
|
|
//HATA 1, 2 & 3
|
|
loss =
|
|
HATApathLoss(LR.frq_mhz, txelev,
|
|
path.elevation[y] +
|
|
(destination.alt *
|
|
METERS_PER_FOOT), dkm, pmenv);
|
|
break;
|
|
case 4:
|
|
// COST231-HATA
|
|
loss =
|
|
ECC33pathLoss(LR.frq_mhz, txelev,
|
|
path.elevation[y] +
|
|
(destination.alt *
|
|
METERS_PER_FOOT), dkm,
|
|
pmenv);
|
|
break;
|
|
case 5:
|
|
// SUI
|
|
loss =
|
|
SUIpathLoss(LR.frq_mhz, txelev,
|
|
path.elevation[y] +
|
|
(destination.alt *
|
|
METERS_PER_FOOT), dkm, pmenv);
|
|
break;
|
|
case 6:
|
|
loss =
|
|
COST231pathLoss(LR.frq_mhz, txelev,
|
|
path.elevation[y] +
|
|
(destination.alt *
|
|
METERS_PER_FOOT), dkm,
|
|
pmenv);
|
|
break;
|
|
case 7:
|
|
// ITU-R P.525 Free space path loss
|
|
loss = FSPLpathLoss(LR.frq_mhz, dkm);
|
|
break;
|
|
case 8:
|
|
// ITWOM 3.0
|
|
point_to_point(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);
|
|
break;
|
|
case 9:
|
|
// Ericsson
|
|
loss =
|
|
EricssonpathLoss(LR.frq_mhz, txelev,
|
|
path.elevation[y] +
|
|
(destination.alt *
|
|
METERS_PER_FOOT), dkm,
|
|
pmenv);
|
|
break;
|
|
|
|
default:
|
|
point_to_point_ITM(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);
|
|
|
|
}
|
|
|
|
if (block)
|
|
elevation =
|
|
((acos(cos_test_angle)) / DEG2RAD) - 90.0;
|
|
else
|
|
elevation =
|
|
((acos(cos_xmtr_angle)) / DEG2RAD) - 90.0;
|
|
|
|
/* Integrate the antenna's radiation
|
|
pattern into the overall path loss. */
|
|
|
|
x = (int)rint(10.0 * (10.0 - elevation));
|
|
|
|
if (x >= 0 && x <= 1000) {
|
|
pattern =
|
|
(double)LR.antenna_pattern[(int)azimuth][x];
|
|
|
|
if (pattern != 0.0)
|
|
patterndB = 20.0 * log10(pattern);
|
|
}
|
|
|
|
else
|
|
patterndB = 0.0;
|
|
|
|
total_loss = loss - patterndB;
|
|
|
|
if (total_loss > maxloss)
|
|
maxloss = total_loss;
|
|
|
|
if (total_loss < minloss)
|
|
minloss = total_loss;
|
|
}
|
|
|
|
distance = Distance(source, destination);
|
|
|
|
if (distance != 0.0) {
|
|
free_space_loss =
|
|
36.6 + (20.0 * log10(LR.frq_mhz)) +
|
|
(20.0 * log10(distance));
|
|
fprintf(fd2, "Free space path loss: %.2f dB\n",
|
|
free_space_loss);
|
|
}
|
|
|
|
fprintf(fd2, "Computed path loss: %.2f dB\n", loss);
|
|
|
|
if (free_space_loss != 0.0)
|
|
fprintf(fd2,
|
|
"Attenuation due to terrain shielding: %.2f dB\n",
|
|
loss - free_space_loss);
|
|
|
|
if (patterndB != 0.0)
|
|
fprintf(fd2,
|
|
"Total path loss including %s antenna pattern: %.2f dB\n",
|
|
source.name, total_loss);
|
|
|
|
if (LR.erp != 0.0) {
|
|
field_strength =
|
|
(139.4 + (20.0 * log10(LR.frq_mhz)) - total_loss) +
|
|
(10.0 * log10(LR.erp / 1000.0));
|
|
|
|
/* dBm is referenced to EIRP */
|
|
|
|
rxp = eirp / (pow(10.0, (total_loss / 10.0)));
|
|
dBm = 10.0 * (log10(rxp * 1000.0));
|
|
power_density =
|
|
(eirp /
|
|
(pow
|
|
(10.0, (total_loss - free_space_loss) / 10.0)));
|
|
/* divide by 4*PI*distance_in_meters squared */
|
|
power_density /= (4.0 * PI * distance * distance *
|
|
2589988.11);
|
|
|
|
fprintf(fd2, "Field strength at %s: %.2f dBuV/meter\n",
|
|
destination.name, field_strength);
|
|
fprintf(fd2, "Signal power level at %s: %+.2f dBm\n",
|
|
destination.name, dBm);
|
|
fprintf(fd2,
|
|
"Signal power density at %s: %+.2f dBW per square meter\n",
|
|
destination.name, 10.0 * log10(power_density));
|
|
voltage =
|
|
1.0e6 * sqrt(50.0 *
|
|
(eirp /
|
|
(pow
|
|
(10.0,
|
|
(total_loss - 2.14) / 10.0))));
|
|
fprintf(fd2,
|
|
"Voltage across 50 ohm dipole at %s: %.2f uV (%.2f dBuV)\n",
|
|
destination.name, voltage,
|
|
20.0 * log10(voltage));
|
|
|
|
voltage =
|
|
1.0e6 * sqrt(75.0 *
|
|
(eirp /
|
|
(pow
|
|
(10.0,
|
|
(total_loss - 2.14) / 10.0))));
|
|
fprintf(fd2,
|
|
"Voltage across 75 ohm dipole at %s: %.2f uV (%.2f dBuV)\n",
|
|
destination.name, voltage,
|
|
20.0 * log10(voltage));
|
|
}
|
|
|
|
if (propmodel == 1) {
|
|
fprintf(fd2, "Longley-Rice model error number: %d",
|
|
errnum);
|
|
|
|
switch (errnum) {
|
|
case 0:
|
|
fprintf(fd2, " (No error)\n");
|
|
break;
|
|
|
|
case 1:
|
|
fprintf(fd2,
|
|
"\n Warning: Some parameters are nearly out of range.\n");
|
|
fprintf(fd2,
|
|
" Results should be used with caution.\n");
|
|
break;
|
|
|
|
case 2:
|
|
fprintf(fd2,
|
|
"\n Note: Default parameters have been substituted for impossible ones.\n");
|
|
break;
|
|
|
|
case 3:
|
|
fprintf(fd2,
|
|
"\n Warning: A combination of parameters is out of range for this model.\n");
|
|
fprintf(fd2,
|
|
" Results should be used with caution.\n");
|
|
break;
|
|
|
|
default:
|
|
fprintf(fd2,
|
|
"\n Warning: Some parameters are out of range for this model.\n");
|
|
fprintf(fd2,
|
|
" Results should be used with caution.\n");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
ObstructionAnalysis(source, destination, LR.frq_mhz, fd2);
|
|
|
|
fclose(fd2);
|
|
|
|
fprintf(stdout,
|
|
"Path loss (dB), Received Power (dBm), Field strength (dBuV):\n%.1f\n%.1f\n%.1f",
|
|
loss, dBm, field_strength);
|
|
|
|
/* Skip plotting the graph if ONLY a path-loss report is needed. */
|
|
|
|
if (graph_it) {
|
|
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("ppa.gp", "w");
|
|
|
|
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 \"Path Loss Profile Along Path Between %s and %s (%.2f%c azimuth)\"\n",
|
|
destination.name, source.name, Azimuth(destination,
|
|
source), 176);
|
|
|
|
if (metric)
|
|
fprintf(fd,
|
|
"set xlabel \"Distance Between %s and %s (%.2f kilometers)\"\n",
|
|
destination.name, source.name,
|
|
KM_PER_MILE * Distance(destination, source));
|
|
else
|
|
fprintf(fd,
|
|
"set xlabel \"Distance Between %s and %s (%.2f miles)\"\n",
|
|
destination.name, source.name,
|
|
Distance(destination, source));
|
|
|
|
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)");
|
|
|
|
fprintf(fd, "\"\nset output \"%s.%s\"\n", basename, ext);
|
|
fprintf(fd,
|
|
"plot \"profile.gp\" title \"Path Loss\" with lines\n");
|
|
|
|
fclose(fd);
|
|
|
|
x = system("gnuplot ppa.gp");
|
|
|
|
if (x != -1) {
|
|
if (gpsav == 0) {
|
|
//unlink("ppa.gp");
|
|
//unlink("profile.gp");
|
|
//unlink("reference.gp");
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
fprintf(stderr,
|
|
"\n*** ERROR: Error occurred invoking gnuplot!\n");
|
|
}
|
|
|
|
}
|
|
|
|
void SeriesData(struct site source, struct site destination, char *name,
|
|
unsigned char fresnel_plot, unsigned char normalised)
|
|
{
|
|
|
|
int x, y, z;
|
|
char basename[255], term[30], ext[15], profilename[255],
|
|
referencename[255], cluttername[255], curvaturename[255],
|
|
fresnelname[255], fresnel60name[255];
|
|
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, minterrain =
|
|
100000.0, minearth = 100000.0;
|
|
struct site remote;
|
|
FILE *fd = NULL, *fd1 = NULL, *fd2 = NULL, *fd3 = NULL, *fd4 =
|
|
NULL, *fd5 = NULL;
|
|
|
|
ReadPath(destination, source);
|
|
azimuth = Azimuth(destination, source);
|
|
distance = Distance(destination, source);
|
|
refangle = ElevationAngle(destination, source);
|
|
b = GetElevation(destination) + destination.alt + earthradius;
|
|
|
|
if (fresnel_plot) {
|
|
lambda = 9.8425e8 / (LR.frq_mhz * 1e6);
|
|
d = 5280.0 * path.distance[path.length - 1];
|
|
}
|
|
|
|
if (normalised) {
|
|
ed = GetElevation(destination);
|
|
es = GetElevation(source);
|
|
nb = -destination.alt - ed;
|
|
nm = (-source.alt - es - nb) / (path.distance[path.length - 1]);
|
|
}
|
|
|
|
strcpy(profilename, name);
|
|
strcat(profilename, "_profile\0");
|
|
strcpy(referencename, name);
|
|
strcat(referencename, "_reference\0");
|
|
strcpy(cluttername, name);
|
|
strcat(cluttername, "_clutter\0");
|
|
strcpy(curvaturename, name);
|
|
strcat(curvaturename, "_curvature\0");
|
|
strcpy(fresnelname, name);
|
|
strcat(fresnelname, "_fresnel\0");
|
|
strcpy(fresnel60name, name);
|
|
strcat(fresnel60name, "_fresnel60\0");
|
|
|
|
fd = fopen(profilename, "wb");
|
|
if (clutter > 0.0)
|
|
fd1 = fopen(cluttername, "wb");
|
|
fd2 = fopen(referencename, "wb");
|
|
fd5 = fopen(curvaturename, "wb");
|
|
|
|
if ((LR.frq_mhz >= 20.0) && (LR.frq_mhz <= 100000.0) && fresnel_plot) {
|
|
fd3 = fopen(fresnelname, "wb");
|
|
fd4 = fopen(fresnel60name, "wb");
|
|
}
|
|
|
|
for (x = 0; x < path.length - 1; x++) {
|
|
remote.lat = path.lat[x];
|
|
remote.lon = path.lon[x];
|
|
remote.alt = 0.0;
|
|
terrain = GetElevation(remote);
|
|
if (x == 0)
|
|
terrain += destination.alt; /* RX antenna spike */
|
|
|
|
a = terrain + earthradius;
|
|
cangle = 5280.0 * Distance(destination, remote) / earthradius;
|
|
c = b * sin(refangle * DEG2RAD + HALFPI) / sin(HALFPI -
|
|
refangle *
|
|
DEG2RAD -
|
|
cangle);
|
|
height = a - c;
|
|
|
|
/* Per Fink and Christiansen, Electronics
|
|
* Engineers' Handbook, 1989:
|
|
*
|
|
* H = sqrt(lamba * d1 * (d - d1)/d)
|
|
*
|
|
* where H is the distance from the LOS
|
|
* path to the first Fresnel zone boundary.
|
|
*/
|
|
|
|
if ((LR.frq_mhz >= 20.0) && (LR.frq_mhz <= 100000.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 (normalised) {
|
|
r = -(nm * path.distance[x]) - nb;
|
|
height += r;
|
|
|
|
if ((LR.frq_mhz >= 20.0) && (LR.frq_mhz <= 100000.0)
|
|
&& fresnel_plot) {
|
|
f_zone += r;
|
|
fpt6_zone += r;
|
|
}
|
|
}
|
|
|
|
else
|
|
r = 0.0;
|
|
|
|
if (metric) {
|
|
if (METERS_PER_FOOT * height > 0) {
|
|
fprintf(fd, "%.3f %.3f\n",
|
|
KM_PER_MILE * path.distance[x],
|
|
METERS_PER_FOOT * height);
|
|
}
|
|
|
|
if (fd1 != NULL && x > 0 && x < path.length - 2)
|
|
fprintf(fd1, "%.3f %.3f\n",
|
|
KM_PER_MILE * path.distance[x],
|
|
METERS_PER_FOOT * (terrain ==
|
|
0.0 ? height
|
|
: (height +
|
|
clutter)));
|
|
|
|
fprintf(fd2, "%.3f %.3f\n",
|
|
KM_PER_MILE * path.distance[x],
|
|
METERS_PER_FOOT * r);
|
|
fprintf(fd5, "%.3f %.3f\n",
|
|
KM_PER_MILE * path.distance[x],
|
|
METERS_PER_FOOT * (height - terrain));
|
|
|
|
}
|
|
|
|
else {
|
|
fprintf(fd, "%.3f %.3f\n", path.distance[x], height);
|
|
|
|
if (fd1 != NULL && x > 0 && x < path.length - 2)
|
|
fprintf(fd1, "%.3f %.3f\n", path.distance[x],
|
|
(terrain ==
|
|
0.0 ? height : (height + clutter)));
|
|
|
|
fprintf(fd2, "%.3f %.3f\n", path.distance[x], r);
|
|
fprintf(fd5, "%.3f %.3f\n", path.distance[x],
|
|
height - terrain);
|
|
}
|
|
|
|
if ((LR.frq_mhz >= 20.0) && (LR.frq_mhz <= 100000.0)
|
|
&& fresnel_plot) {
|
|
if (metric) {
|
|
fprintf(fd3, "%.3f %.3f\n",
|
|
KM_PER_MILE * path.distance[x],
|
|
METERS_PER_FOOT * f_zone);
|
|
fprintf(fd4, "%.3f %.3f\n",
|
|
KM_PER_MILE * path.distance[x],
|
|
METERS_PER_FOOT * fpt6_zone);
|
|
}
|
|
|
|
else {
|
|
fprintf(fd3, "%.3f %.3f\n", path.distance[x],
|
|
f_zone);
|
|
fprintf(fd4, "%.3f %.3f\n", path.distance[x],
|
|
fpt6_zone);
|
|
}
|
|
|
|
if (f_zone < minheight)
|
|
minheight = f_zone;
|
|
}
|
|
|
|
if ((height + clutter) > maxheight)
|
|
maxheight = height + clutter;
|
|
|
|
if (height < minheight)
|
|
minheight = height;
|
|
|
|
if (r > maxheight)
|
|
maxheight = r;
|
|
|
|
if (terrain < minterrain)
|
|
minterrain = terrain;
|
|
|
|
if ((height - terrain) < minearth)
|
|
minearth = height - terrain;
|
|
} // End of loop
|
|
|
|
if (normalised)
|
|
r = -(nm * path.distance[path.length - 1]) - nb;
|
|
else
|
|
r = 0.0;
|
|
|
|
if (metric) {
|
|
fprintf(fd, "%.3f %.3f",
|
|
KM_PER_MILE * path.distance[path.length - 1],
|
|
METERS_PER_FOOT * r);
|
|
fprintf(fd2, "%.3f %.3f",
|
|
KM_PER_MILE * path.distance[path.length - 1],
|
|
METERS_PER_FOOT * r);
|
|
}
|
|
|
|
else {
|
|
fprintf(fd, "%.3f %.3f", path.distance[path.length - 1], r);
|
|
fprintf(fd2, "%.3f %.3f", path.distance[path.length - 1], r);
|
|
}
|
|
|
|
if ((LR.frq_mhz >= 20.0) && (LR.frq_mhz <= 100000.0) && fresnel_plot) {
|
|
if (metric) {
|
|
fprintf(fd3, "%.3f %.3f",
|
|
KM_PER_MILE * path.distance[path.length - 1],
|
|
METERS_PER_FOOT * r);
|
|
fprintf(fd4, "%.3f %.3f",
|
|
KM_PER_MILE * path.distance[path.length - 1],
|
|
METERS_PER_FOOT * r);
|
|
}
|
|
|
|
else {
|
|
fprintf(fd3, "%.3f %.3f",
|
|
path.distance[path.length - 1], r);
|
|
fprintf(fd4, "%.3f %.3f",
|
|
path.distance[path.length - 1], r);
|
|
}
|
|
}
|
|
|
|
if (r > maxheight)
|
|
maxheight = r;
|
|
|
|
if (r < minheight)
|
|
minheight = r;
|
|
|
|
fclose(fd);
|
|
|
|
if (fd1 != NULL)
|
|
fclose(fd1);
|
|
|
|
fclose(fd2);
|
|
fclose(fd5);
|
|
|
|
if ((LR.frq_mhz >= 20.0) && (LR.frq_mhz <= 100000.0) && fresnel_plot) {
|
|
fclose(fd3);
|
|
fclose(fd4);
|
|
}
|
|
|
|
if (name[0] == '.') {
|
|
strncpy(basename, "profile\0", 8);
|
|
strncpy(term, "png\0", 4);
|
|
strncpy(ext, "png\0", 4);
|
|
}
|
|
|
|
else {
|
|
|
|
ext[0] = 0;
|
|
y = strlen(name);
|
|
strncpy(basename, name, 254);
|
|
|
|
for (x = y - 1; x > 0 && name[x] != '.'; x--) ;
|
|
|
|
if (x > 0) {
|
|
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;
|
|
term[z - (x + 1)] = 0;
|
|
basename[x] = 0;
|
|
}
|
|
|
|
if (ext[0] == 0) {
|
|
strncpy(term, "png\0", 4);
|
|
strncpy(ext, "png\0", 4);
|
|
}
|
|
}
|
|
|
|
fprintf(stdout, "\n");
|
|
fflush(stdout);
|
|
|
|
}
|