]> git.mxchange.org Git - flightgear.git/blob - src/Radio/radio.cxx
Add function to calculate polarization loss
[flightgear.git] / src / Radio / radio.cxx
1 // radio.cxx -- implementation of FGRadio
2 // Class to manage radio propagation using the ITM model
3 // Written by Adrian Musceac, started August 2011.
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19
20
21 #ifdef HAVE_CONFIG_H
22 #  include <config.h>
23 #endif
24
25 #include <math.h>
26
27 #include <stdlib.h>
28 #include <deque>
29 #include "radio.hxx"
30 #include <simgear/scene/material/mat.hxx>
31 #include <Scenery/scenery.hxx>
32
33 #define WITH_POINT_TO_POINT 1
34 #include "itm.cpp"
35
36
37 FGRadioTransmission::FGRadioTransmission() {
38         
39         
40         _receiver_sensitivity = -110.0; // typical AM receiver sensitivity seems to be 0.8 microVolt at 12dB SINAD
41         
42         /** AM transmitter power in dBm.
43         *       Typical output powers for ATC ground equipment, VHF-UHF:
44         *       40 dBm - 10 W (ground, clearance)
45         *       44 dBm - 20 W (tower)
46         *       47 dBm - 50 W (center, sectors)
47         *       50 dBm - 100 W (center, sectors)
48         *       53 dBm - 200 W (sectors, on directional arrays)
49         **/
50         _transmitter_power = 43.0;
51         
52         _tx_antenna_height = 2.0; // TX antenna height above ground level
53         
54         _rx_antenna_height = 2.0; // RX antenna height above ground level
55         
56         
57         _rx_antenna_gain = 1.0; // gain expressed in dBi
58         _tx_antenna_gain = 1.0;
59         
60         _rx_line_losses = 2.0;  // to be configured for each station
61         _tx_line_losses = 2.0;
62         
63         _polarization = 1; // default vertical
64         
65         _propagation_model = 2; 
66         _terrain_sampling_distance = fgGetDouble("/sim/radio/sampling-distance", 90.0); // regular SRTM is 90 meters
67 }
68
69 FGRadioTransmission::~FGRadioTransmission() 
70 {
71 }
72
73
74 double FGRadioTransmission::getFrequency(int radio) {
75         double freq = 118.0;
76         switch (radio) {
77                 case 1:
78                         freq = fgGetDouble("/instrumentation/comm[0]/frequencies/selected-mhz");
79                         break;
80                 case 2:
81                         freq = fgGetDouble("/instrumentation/comm[1]/frequencies/selected-mhz");
82                         break;
83                 default:
84                         freq = fgGetDouble("/instrumentation/comm[0]/frequencies/selected-mhz");
85                         
86         }
87         return freq;
88 }
89
90 /*** TODO: receive multiplayer chat message and voice
91 ***/
92 void FGRadioTransmission::receiveChat(SGGeod tx_pos, double freq, string text, int ground_to_air) {
93
94 }
95
96 /*** TODO: receive navaid 
97 ***/
98 double FGRadioTransmission::receiveNav(SGGeod tx_pos, double freq, int transmission_type) {
99         
100         // typical VOR/LOC transmitter power appears to be 200 Watt ~ 53 dBm
101         // vor/loc typical sensitivity between -107 and -101 dBm
102         // glideslope sensitivity between -85 and -81 dBm
103         if ( _propagation_model == 1) {
104                 return LOS_calculate_attenuation(tx_pos, freq, 1);
105         }
106         else if ( _propagation_model == 2) {
107                 return ITM_calculate_attenuation(tx_pos, freq, 1);
108         }
109         
110         return -1;
111
112 }
113
114 /*** Receive ATC radio communication as text
115 ***/
116 void FGRadioTransmission::receiveATC(SGGeod tx_pos, double freq, string text, int ground_to_air) {
117
118         
119         if(ground_to_air == 1) {
120                 _transmitter_power += 6.0;
121                 _tx_antenna_height += 30.0;
122                 _tx_antenna_gain += 3.0; 
123         }
124         
125         
126         double comm1 = getFrequency(1);
127         double comm2 = getFrequency(2);
128         if ( !(fabs(freq - comm1) <= 0.0001) &&  !(fabs(freq - comm2) <= 0.0001) ) {
129                 return;
130         }
131         else {
132         
133                 if ( _propagation_model == 0) {
134                         fgSetString("/sim/messages/atc", text.c_str());
135                 }
136                 else if ( _propagation_model == 1 ) {
137                         // TODO: free space, round earth
138                         double signal = LOS_calculate_attenuation(tx_pos, freq, ground_to_air);
139                         if (signal <= 0.0) {
140                                 return;
141                         }
142                         else {
143                                 
144                                 fgSetString("/sim/messages/atc", text.c_str());
145                                 /** write signal strength above threshold to the property tree
146                                 *       to implement a simple S-meter just divide by 3 dB per grade (VHF norm)
147                                 **/
148                                 fgSetDouble("/sim/radio/comm1-signal", signal);
149                         }
150                 }
151                 else if ( _propagation_model == 2 ) {
152                         // Use ITM propagation model
153                         double signal = ITM_calculate_attenuation(tx_pos, freq, ground_to_air);
154                         if (signal <= 0.0) {
155                                 return;
156                         }
157                         if ((signal > 0.0) && (signal < 12.0)) {
158                                 /** for low SNR values implement a way to make the conversation
159                                 *       hard to understand but audible
160                                 *       in the real world, the receiver AGC fails to capture the slope
161                                 *       and the signal, due to being amplitude modulated, decreases volume after demodulation
162                                 *       the workaround below is more akin to what would happen on a FM transmission
163                                 *       therefore the correct way would be to work on the volume
164                                 **/
165                                 /*
166                                 string hash_noise = " ";
167                                 int reps = (int) (fabs(floor(signal - 11.0)) * 2);
168                                 int t_size = text.size();
169                                 for (int n = 1; n <= reps; ++n) {
170                                         int pos = rand() % (t_size -1);
171                                         text.replace(pos,1, hash_noise);
172                                 }
173                                 */
174                                 double volume = (fabs(signal - 12.0) / 12);
175                                 double old_volume = fgGetDouble("/sim/sound/voices/voice/volume");
176                                 SG_LOG(SG_GENERAL, SG_BULK, "Usable signal at limit: " << signal);
177                                 //cerr << "Usable signal at limit: " << signal << endl;
178                                 fgSetDouble("/sim/sound/voices/voice/volume", volume);
179                                 fgSetString("/sim/messages/atc", text.c_str());
180                                 fgSetDouble("/sim/radio/comm1-signal", signal);
181                                 fgSetDouble("/sim/sound/voices/voice/volume", old_volume);
182                         }
183                         else {
184                                 fgSetString("/sim/messages/atc", text.c_str());
185                                 /** write signal strength above threshold to the property tree
186                                 *       to implement a simple S-meter just divide by 3 dB per grade (VHF norm)
187                                 **/
188                                 fgSetDouble("/sim/radio/comm1-signal", signal);
189                         }
190                         
191                 }
192                 
193         }
194         
195 }
196
197 /***  Implement radio attenuation               
198           based on the Longley-Rice propagation model
199 ***/
200 double FGRadioTransmission::ITM_calculate_attenuation(SGGeod pos, double freq, int transmission_type) {
201
202         
203         
204         /** ITM default parameters 
205                 TODO: take them from tile materials (especially for sea)?
206         **/
207         double eps_dielect=15.0;
208         double sgm_conductivity = 0.005;
209         double eno = 301.0;
210         double frq_mhz;
211         if( (freq < 118.0) || (freq > 137.0) )
212                 frq_mhz = 125.0;        // sane value, middle of bandplan
213         else
214                 frq_mhz = freq;
215         int radio_climate = 5;          // continental temperate
216         int pol= _polarization; 
217         double conf = 0.90;     // 90% of situations and time, take into account speed
218         double rel = 0.90;      
219         double dbloss;
220         char strmode[150];
221         int p_mode = 0; // propgation mode selector: 0 LOS, 1 diffraction dominant, 2 troposcatter
222         double horizons[2];
223         int errnum;
224         
225         double clutter_loss = 0.0;      // loss due to vegetation and urban
226         double tx_pow = _transmitter_power;
227         double ant_gain = _rx_antenna_gain + _tx_antenna_gain;
228         double signal = 0.0;
229         
230         
231         double link_budget = tx_pow - _receiver_sensitivity - _rx_line_losses - _tx_line_losses + ant_gain;     
232
233         FGScenery * scenery = globals->get_scenery();
234         
235         double own_lat = fgGetDouble("/position/latitude-deg");
236         double own_lon = fgGetDouble("/position/longitude-deg");
237         double own_alt_ft = fgGetDouble("/position/altitude-ft");
238         double own_alt= own_alt_ft * SG_FEET_TO_METER;
239         
240         
241         //cerr << "ITM:: pilot Lat: " << own_lat << ", Lon: " << own_lon << ", Alt: " << own_alt << endl;
242         
243         SGGeod own_pos = SGGeod::fromDegM( own_lon, own_lat, own_alt );
244         SGGeod max_own_pos = SGGeod::fromDegM( own_lon, own_lat, SG_MAX_ELEVATION_M );
245         SGGeoc center = SGGeoc::fromGeod( max_own_pos );
246         SGGeoc own_pos_c = SGGeoc::fromGeod( own_pos );
247         
248         
249         double sender_alt_ft,sender_alt;
250         double transmitter_height=0.0;
251         double receiver_height=0.0;
252         SGGeod sender_pos = pos;
253         
254         sender_alt_ft = sender_pos.getElevationFt();
255         sender_alt = sender_alt_ft * SG_FEET_TO_METER;
256         SGGeod max_sender_pos = SGGeod::fromGeodM( pos, SG_MAX_ELEVATION_M );
257         SGGeoc sender_pos_c = SGGeoc::fromGeod( sender_pos );
258         //cerr << "ITM:: sender Lat: " << parent->getLatitude() << ", Lon: " << parent->getLongitude() << ", Alt: " << sender_alt << endl;
259         
260         double point_distance= _terrain_sampling_distance; 
261         double course = SGGeodesy::courseRad(own_pos_c, sender_pos_c);
262         double distance_m = SGGeodesy::distanceM(own_pos, sender_pos);
263         double probe_distance = 0.0;
264         /** If distance larger than this value (300 km), assume reception imposssible */
265         if (distance_m > 300000)
266                 return -1.0;
267         /** If above 8000 meters, consider LOS mode and calculate free-space att */
268         if (own_alt > 8000) {
269                 dbloss = 20 * log10(distance_m) +20 * log10(frq_mhz) -27.55;
270                 SG_LOG(SG_GENERAL, SG_BULK,
271                         "ITM Free-space mode:: Link budget: " << link_budget << ", Attenuation: " << dbloss << " dBm, free-space attenuation");
272                 //cerr << "ITM Free-space mode:: Link budget: " << link_budget << ", Attenuation: " << dbloss << " dBm, free-space attenuation" << endl;
273                 signal = link_budget - dbloss;
274                 return signal;
275         }
276         
277                 
278         int max_points = (int)floor(distance_m / point_distance);
279         double delta_last = fmod(distance_m, point_distance);
280         
281         deque<double> _elevations;
282         deque<string> materials;
283         
284
285         double elevation_under_pilot = 0.0;
286         if (scenery->get_elevation_m( max_own_pos, elevation_under_pilot, NULL )) {
287                 receiver_height = own_alt - elevation_under_pilot; 
288         }
289
290         double elevation_under_sender = 0.0;
291         if (scenery->get_elevation_m( max_sender_pos, elevation_under_sender, NULL )) {
292                 transmitter_height = sender_alt - elevation_under_sender;
293         }
294         else {
295                 transmitter_height = sender_alt;
296         }
297         
298         
299         transmitter_height += _tx_antenna_height;
300         receiver_height += _rx_antenna_height;
301         
302         
303         SG_LOG(SG_GENERAL, SG_BULK,
304                         "ITM:: RX-height: " << receiver_height << " meters, TX-height: " << transmitter_height << " meters, Distance: " << distance_m << " meters");
305         cerr << "ITM:: RX-height: " << receiver_height << " meters, TX-height: " << transmitter_height << " meters, Distance: " << distance_m << " meters" << endl;
306         
307         unsigned int e_size = (deque<unsigned>::size_type)max_points;
308         
309         while (_elevations.size() <= e_size) {
310                 probe_distance += point_distance;
311                 SGGeod probe = SGGeod::fromGeoc(center.advanceRadM( course, probe_distance ));
312                 const SGMaterial *mat = 0;
313                 double elevation_m = 0.0;
314         
315                 if (scenery->get_elevation_m( probe, elevation_m, &mat )) {
316                         if((transmission_type == 3) || (transmission_type == 4)) {
317                                 _elevations.push_back(elevation_m);
318                                 if(mat) {
319                                         const std::vector<string> mat_names = mat->get_names();
320                                         materials.push_back(mat_names[0]);
321                                 }
322                                 else {
323                                         materials.push_back("None");
324                                 }
325                         }
326                         else {
327                                  _elevations.push_front(elevation_m);
328                                  if(mat) {
329                                          const std::vector<string> mat_names = mat->get_names();
330                                          materials.push_front(mat_names[0]);
331                                 }
332                                 else {
333                                         materials.push_front("None");
334                                 }
335                         }
336                 }
337                 else {
338                         if((transmission_type == 3) || (transmission_type == 4)) {
339                                 _elevations.push_back(0.0);
340                                 materials.push_back("None");
341                         }
342                         else {
343                                 _elevations.push_front(0.0);
344                                 materials.push_front("None");
345                         }
346                 }
347         }
348         if((transmission_type == 3) || (transmission_type == 4)) {
349                 _elevations.push_front(elevation_under_pilot);
350                 if (delta_last > (point_distance / 2) )                 // only add last point if it's farther than half point_distance
351                         _elevations.push_back(elevation_under_sender);
352         }
353         else {
354                 _elevations.push_back(elevation_under_pilot);
355                 if (delta_last > (point_distance / 2) )
356                         _elevations.push_front(elevation_under_sender);
357         }
358         
359         
360         double num_points= (double)_elevations.size();
361
362         _elevations.push_front(point_distance);
363         _elevations.push_front(num_points -1);
364         int size = _elevations.size();
365         double itm_elev[size];
366         for(int i=0;i<size;i++) {
367                 itm_elev[i]=_elevations[i];
368                 //cerr << "ITM:: itm_elev: " << _elevations[i] << endl;
369         }
370
371         if((transmission_type == 3) || (transmission_type == 4)) {
372                 // the sender and receiver roles are switched
373                 point_to_point(itm_elev, receiver_height, transmitter_height,
374                         eps_dielect, sgm_conductivity, eno, frq_mhz, radio_climate,
375                         pol, conf, rel, dbloss, strmode, p_mode, horizons, errnum);
376                 if( fgGetBool( "/sim/radio/use-clutter-attenuation", false ) )
377                         clutterLoss(frq_mhz, distance_m, itm_elev, materials, receiver_height, transmitter_height, p_mode, horizons, clutter_loss);
378         }
379         else {
380                 point_to_point(itm_elev, transmitter_height, receiver_height,
381                         eps_dielect, sgm_conductivity, eno, frq_mhz, radio_climate,
382                         pol, conf, rel, dbloss, strmode, p_mode, horizons, errnum);
383                 if( fgGetBool( "/sim/radio/use-clutter-attenuation", false ) )
384                         clutterLoss(frq_mhz, distance_m, itm_elev, materials, transmitter_height, receiver_height, p_mode, horizons, clutter_loss);
385         }
386         SG_LOG(SG_GENERAL, SG_BULK,
387                         "ITM:: Link budget: " << link_budget << ", Attenuation: " << dbloss << " dBm, " << strmode << ", Error: " << errnum);
388         cerr << "ITM:: Link budget: " << link_budget << ", Attenuation: " << dbloss << " dBm, " << strmode << ", Error: " << errnum << endl;
389         
390         cerr << "Clutter loss: " << clutter_loss << endl;
391         //if (errnum == 4)      // if parameters are outside sane values for lrprop, the alternative method is used
392         //      return -1;
393         signal = link_budget - dbloss - clutter_loss;
394         return signal;
395
396 }
397
398 /*** Calculate losses due to vegetation and urban clutter (WIP)
399 *        We are only worried about clutter loss, terrain influence 
400 *        on the first Fresnel zone is calculated in the ITM functions
401 ***/
402 void FGRadioTransmission::clutterLoss(double freq, double distance_m, double itm_elev[], deque<string> materials,
403         double transmitter_height, double receiver_height, int p_mode,
404         double horizons[], double &clutter_loss) {
405         
406         distance_m = itm_elev[0] * itm_elev[1]; // only consider elevation points
407         
408         if (p_mode == 0) {      // LOS: take each point and see how clutter height affects first Fresnel zone
409                 int mat = 0;
410                 int j=1; 
411                 for (int k=3;k < (int)(itm_elev[0]) + 2;k++) {
412                         
413                         double clutter_height = 0.0;    // mean clutter height for a certain terrain type
414                         double clutter_density = 0.0;   // percent of reflected wave
415                         get_material_properties(materials[mat], clutter_height, clutter_density);
416                         
417                         double grad = fabs(itm_elev[2] + transmitter_height - itm_elev[(int)itm_elev[0] + 2] + receiver_height) / distance_m;
418                         // First Fresnel radius
419                         double frs_rad = 548 * sqrt( (j * itm_elev[1] * (itm_elev[0] - j) * itm_elev[1] / 1000000) / (  distance_m * freq / 1000) );
420                         
421                         //double earth_h = distance_m * (distance_m - j * itm_elev[1]) / ( 1000000 * 12.75 * 1.33 );    // K=4/3
422                         
423                         double min_elev = SGMiscd::min(itm_elev[2] + transmitter_height, itm_elev[(int)itm_elev[0] + 2] + receiver_height);
424                         double d1 = j * itm_elev[1];
425                         if ((itm_elev[2] + transmitter_height) > ( itm_elev[(int)itm_elev[0] + 2] + receiver_height) ) {
426                                 d1 = (itm_elev[0] - j) * itm_elev[1];
427                         }
428                         double ray_height = (grad * d1) + min_elev;
429                         
430                         double clearance = ray_height - (itm_elev[k] + clutter_height) - frs_rad * 8/10;                
431                         double intrusion = fabs(clearance);
432                         
433                         if (clearance >= 0) {
434                                 // no losses
435                         }
436                         else if (clearance < 0 && (intrusion < clutter_height)) {
437                                 
438                                 clutter_loss += clutter_density * (intrusion / (frs_rad * 2) ) * (freq/100) * (itm_elev[1]/100);
439                         }
440                         else if (clearance < 0 && (intrusion > clutter_height)) {
441                                 clutter_loss += clutter_density * (clutter_height / (frs_rad * 2 ) ) * (freq/100) * (itm_elev[1]/100);
442                         }
443                         else {
444                                 // no losses
445                         }
446                         j++;
447                         mat++;
448                 }
449                 
450         }
451         else if (p_mode == 1) {         // diffraction
452                 
453                 if (horizons[1] == 0.0) {       //      single horizon: same as above, except pass twice using the highest point
454                         int num_points_1st = (int)floor( horizons[0] * itm_elev[0]/ distance_m ); 
455                         int num_points_2nd = (int)ceil( (distance_m - horizons[0]) * itm_elev[0] / distance_m ); 
456                         //cerr << "Diffraction 1 horizon:: points1: " << num_points_1st << " points2: " << num_points_2nd << endl;
457                         int last = 1;
458                         /** perform the first pass */
459                         int mat = 0;
460                         int j=1; 
461                         for (int k=3;k < num_points_1st + 2;k++) {
462                                 if (num_points_1st < 1)
463                                         break;
464                                 double clutter_height = 0.0;    // mean clutter height for a certain terrain type
465                                 double clutter_density = 0.0;   // percent of reflected wave
466                                 get_material_properties(materials[mat], clutter_height, clutter_density);
467                                 
468                                 double grad = fabs(itm_elev[2] + transmitter_height - itm_elev[num_points_1st + 2] + clutter_height) / distance_m;
469                                 // First Fresnel radius
470                                 double frs_rad = 548 * sqrt( (j * itm_elev[1] * (num_points_1st - j) * itm_elev[1] / 1000000) / ( num_points_1st * itm_elev[1] * freq / 1000) );
471                                 
472                                 //double earth_h = distance_m * (distance_m - j * itm_elev[1]) / ( 1000000 * 12.75 * 1.33 );    // K=4/3
473                                 
474                                 double min_elev = SGMiscd::min(itm_elev[2] + transmitter_height, itm_elev[num_points_1st + 2] + clutter_height);
475                                 double d1 = j * itm_elev[1];
476                                 if ( (itm_elev[2] + transmitter_height) > (itm_elev[num_points_1st + 2] + clutter_height) ) {
477                                         d1 = (num_points_1st - j) * itm_elev[1];
478                                 }
479                                 double ray_height = (grad * d1) + min_elev;
480                                 
481                                 double clearance = ray_height - (itm_elev[k] + clutter_height) - frs_rad * 8/10;                
482                                 double intrusion = fabs(clearance);
483                                 
484                                 if (clearance >= 0) {
485                                         // no losses
486                                 }
487                                 else if (clearance < 0 && (intrusion < clutter_height)) {
488                                         
489                                         clutter_loss += clutter_density * (intrusion / (frs_rad * 2) ) * (freq/100) * (itm_elev[1]/100);
490                                 }
491                                 else if (clearance < 0 && (intrusion > clutter_height)) {
492                                         clutter_loss += clutter_density * (clutter_height / (frs_rad * 2 ) ) * (freq/100) * (itm_elev[1]/100);
493                                 }
494                                 else {
495                                         // no losses
496                                 }
497                                 j++;
498                                 mat++;
499                                 last = k;
500                         }
501                         
502                         /** and the second pass */
503                         mat +=1;
504                         j =1; // first point is diffraction edge, 2nd the RX elevation
505                         for (int k=last+2;k < (int)(itm_elev[0]) + 2;k++) {
506                                 if (num_points_2nd < 1)
507                                         break;
508                                 double clutter_height = 0.0;    // mean clutter height for a certain terrain type
509                                 double clutter_density = 0.0;   // percent of reflected wave
510                                 get_material_properties(materials[mat], clutter_height, clutter_density);
511                                 
512                                 double grad = fabs(itm_elev[last+1] + clutter_height - itm_elev[(int)itm_elev[0] + 2] + receiver_height) / distance_m;
513                                 // First Fresnel radius
514                                 double frs_rad = 548 * sqrt( (j * itm_elev[1] * (num_points_2nd - j) * itm_elev[1] / 1000000) / (  num_points_2nd * itm_elev[1] * freq / 1000) );
515                                 
516                                 //double earth_h = distance_m * (distance_m - j * itm_elev[1]) / ( 1000000 * 12.75 * 1.33 );    // K=4/3
517                                 
518                                 double min_elev = SGMiscd::min(itm_elev[last+1] + clutter_height, itm_elev[(int)itm_elev[0] + 2] + receiver_height);
519                                 double d1 = j * itm_elev[1];
520                                 if ( (itm_elev[last+1] + clutter_height) > (itm_elev[(int)itm_elev[0] + 2] + receiver_height) ) { 
521                                         d1 = (num_points_2nd - j) * itm_elev[1];
522                                 }
523                                 double ray_height = (grad * d1) + min_elev;
524                                 
525                                 double clearance = ray_height - (itm_elev[k] + clutter_height) - frs_rad * 8/10;                
526                                 double intrusion = fabs(clearance);
527                                 
528                                 if (clearance >= 0) {
529                                         // no losses
530                                 }
531                                 else if (clearance < 0 && (intrusion < clutter_height)) {
532                                         
533                                         clutter_loss += clutter_density * (intrusion / (frs_rad * 2) ) * (freq/100) * (itm_elev[1]/100);
534                                 }
535                                 else if (clearance < 0 && (intrusion > clutter_height)) {
536                                         clutter_loss += clutter_density * (clutter_height / (frs_rad * 2 ) ) * (freq/100) * (itm_elev[1]/100);
537                                 }
538                                 else {
539                                         // no losses
540                                 }
541                                 j++;
542                                 mat++;
543                         }
544                         
545                 }
546                 else {  // double horizon: same as single horizon, except there are 3 segments
547                         
548                         int num_points_1st = (int)floor( horizons[0] * itm_elev[0] / distance_m ); 
549                         int num_points_2nd = (int)floor(horizons[1] * itm_elev[0] / distance_m ); 
550                         int num_points_3rd = (int)itm_elev[0] - num_points_1st - num_points_2nd; 
551                         //cerr << "Double horizon:: horizon1: " << horizons[0] << " horizon2: " << horizons[1] << " distance: " << distance_m << endl;
552                         //cerr << "Double horizon:: points1: " << num_points_1st << " points2: " << num_points_2nd << " points3: " << num_points_3rd << endl;
553                         int last = 1;
554                         /** perform the first pass */
555                         int mat = 0;
556                         int j=1; // first point is TX elevation, 2nd is obstruction elevation
557                         for (int k=3;k < num_points_1st +2;k++) {
558                                 if (num_points_1st < 1)
559                                         break;
560                                 double clutter_height = 0.0;    // mean clutter height for a certain terrain type
561                                 double clutter_density = 0.0;   // percent of reflected wave
562                                 get_material_properties(materials[mat], clutter_height, clutter_density);
563                                 
564                                 double grad = fabs(itm_elev[2] + transmitter_height - itm_elev[num_points_1st + 2] + clutter_height) / distance_m;
565                                 // First Fresnel radius
566                                 double frs_rad = 548 * sqrt( (j * itm_elev[1] * (num_points_1st - j) * itm_elev[1] / 1000000) / (  num_points_1st * itm_elev[1] * freq / 1000) );
567                                 
568                                 //double earth_h = distance_m * (distance_m - j * itm_elev[1]) / ( 1000000 * 12.75 * 1.33 );    // K=4/3
569                                 
570                                 double min_elev = SGMiscd::min(itm_elev[2] + transmitter_height, itm_elev[num_points_1st + 2] + clutter_height);
571                                 double d1 = j * itm_elev[1];
572                                 if ( (itm_elev[2] + transmitter_height) > (itm_elev[num_points_1st + 2] + clutter_height) ) {
573                                         d1 = (num_points_1st - j) * itm_elev[1];
574                                 }
575                                 double ray_height = (grad * d1) + min_elev;
576                                 
577                                 double clearance = ray_height - (itm_elev[k] + clutter_height) - frs_rad * 8/10;                
578                                 double intrusion = fabs(clearance);
579                                 
580                                 if (clearance >= 0) {
581                                         // no losses
582                                 }
583                                 else if (clearance < 0 && (intrusion < clutter_height)) {
584                                         
585                                         clutter_loss += clutter_density * (intrusion / (frs_rad * 2) ) * (freq/100) * (itm_elev[1]/100);
586                                 }
587                                 else if (clearance < 0 && (intrusion > clutter_height)) {
588                                         clutter_loss += clutter_density * (clutter_height / (frs_rad * 2 ) ) * (freq/100) * (itm_elev[1]/100);
589                                 }
590                                 else {
591                                         // no losses
592                                 }
593                                 j++;
594                                 last = k;
595                         }
596                         mat +=1;
597                         /** and the second pass */
598                         int last2=1;
599                         j =1; // first point is 1st obstruction elevation, 2nd is 2nd obstruction elevation
600                         for (int k=last+2;k < num_points_1st + num_points_2nd +2;k++) {
601                                 if (num_points_2nd < 1)
602                                         break;
603                                 double clutter_height = 0.0;    // mean clutter height for a certain terrain type
604                                 double clutter_density = 0.0;   // percent of reflected wave
605                                 get_material_properties(materials[mat], clutter_height, clutter_density);
606                                 
607                                 double grad = fabs(itm_elev[last+1] + clutter_height - itm_elev[num_points_1st + num_points_2nd + 2] + clutter_height) / distance_m;
608                                 // First Fresnel radius
609                                 double frs_rad = 548 * sqrt( (j * itm_elev[1] * (num_points_2nd - j) * itm_elev[1] / 1000000) / (  num_points_2nd * itm_elev[1] * freq / 1000) );
610                                 
611                                 //double earth_h = distance_m * (distance_m - j * itm_elev[1]) / ( 1000000 * 12.75 * 1.33 );    // K=4/3
612                                 
613                                 double min_elev = SGMiscd::min(itm_elev[last+1] + clutter_height, itm_elev[num_points_1st + num_points_2nd +2] + clutter_height);
614                                 double d1 = j * itm_elev[1];
615                                 if ( (itm_elev[last+1] + clutter_height) > (itm_elev[num_points_1st + num_points_2nd + 2] + clutter_height) ) { 
616                                         d1 = (num_points_2nd - j) * itm_elev[1];
617                                 }
618                                 double ray_height = (grad * d1) + min_elev;
619                                 
620                                 double clearance = ray_height - (itm_elev[k] + clutter_height) - frs_rad * 8/10;                
621                                 double intrusion = fabs(clearance);
622                                 
623                                 if (clearance >= 0) {
624                                         // no losses
625                                 }
626                                 else if (clearance < 0 && (intrusion < clutter_height)) {
627                                         
628                                         clutter_loss += clutter_density * (intrusion / (frs_rad * 2) ) * (freq/100) * (itm_elev[1]/100);
629                                 }
630                                 else if (clearance < 0 && (intrusion > clutter_height)) {
631                                         clutter_loss += clutter_density * (clutter_height / (frs_rad * 2 ) ) * (freq/100) * (itm_elev[1]/100);
632                                 }
633                                 else {
634                                         // no losses
635                                 }
636                                 j++;
637                                 mat++;
638                                 last2 = k;
639                         }
640                         
641                         /** third and final pass */
642                         mat +=1;
643                         j =1; // first point is 2nd obstruction elevation, 3rd is RX elevation
644                         for (int k=last2+2;k < (int)itm_elev[0] + 2;k++) {
645                                 if (num_points_3rd < 1)
646                                         break;
647                                 double clutter_height = 0.0;    // mean clutter height for a certain terrain type
648                                 double clutter_density = 0.0;   // percent of reflected wave
649                                 get_material_properties(materials[mat], clutter_height, clutter_density);
650                                 
651                                 double grad = fabs(itm_elev[last2+1] + clutter_height - itm_elev[(int)itm_elev[0] + 2] + receiver_height) / distance_m;
652                                 // First Fresnel radius
653                                 double frs_rad = 548 * sqrt( (j * itm_elev[1] * (num_points_3rd - j) * itm_elev[1] / 1000000) / (  num_points_3rd * itm_elev[1] * freq / 1000) );
654                                 
655                                 
656                                 //double earth_h = distance_m * (distance_m - j * itm_elev[1]) / ( 1000000 * 12.75 * 1.33 );    // K=4/3
657                                 
658                                 double min_elev = SGMiscd::min(itm_elev[last2+1] + clutter_height, itm_elev[(int)itm_elev[0] + 2] + receiver_height);
659                                 double d1 = j * itm_elev[1];
660                                 if ( (itm_elev[last2+1] + clutter_height) > (itm_elev[(int)itm_elev[0] + 2] + receiver_height) ) { 
661                                         d1 = (num_points_3rd - j) * itm_elev[1];
662                                 }
663                                 double ray_height = (grad * d1) + min_elev;
664                                 
665                                 double clearance = ray_height - (itm_elev[k] + clutter_height) - frs_rad * 8/10;                
666                                 double intrusion = fabs(clearance);
667                                 
668                                 if (clearance >= 0) {
669                                         // no losses
670                                 }
671                                 else if (clearance < 0 && (intrusion < clutter_height)) {
672                                         
673                                         clutter_loss += clutter_density * (intrusion / (frs_rad * 2) ) * (freq/100) * (itm_elev[1]/100);
674                                 }
675                                 else if (clearance < 0 && (intrusion > clutter_height)) {
676                                         clutter_loss += clutter_density * (clutter_height / (frs_rad * 2 ) ) * (freq/100) * (itm_elev[1]/100);
677                                 }
678                                 else {
679                                         // no losses
680                                 }
681                                 j++;
682                                 mat++;
683                                 
684                         }
685                         
686                 }
687         }
688         else if (p_mode == 2) {         //      troposcatter: ignore ground clutter for now...
689                 clutter_loss = 0.0;
690         }
691         
692 }
693
694 /***    Temporary material properties database
695 *               height: median clutter height
696 *               density: radiowave attenuation factor
697 ***/
698 void FGRadioTransmission::get_material_properties(string mat_name, double &height, double &density) {
699         
700         if(mat_name == "Landmass") {
701                 height = 15.0;
702                 density = 0.2;
703         }
704
705         else if(mat_name == "SomeSort") {
706                 height = 15.0;
707                 density = 0.2;
708         }
709
710         else if(mat_name == "Island") {
711                 height = 15.0;
712                 density = 0.2;
713         }
714         else if(mat_name == "Default") {
715                 height = 15.0;
716                 density = 0.2;
717         }
718         else if(mat_name == "EvergreenBroadCover") {
719                 height = 20.0;
720                 density = 0.2;
721         }
722         else if(mat_name == "EvergreenForest") {
723                 height = 20.0;
724                 density = 0.2;
725         }
726         else if(mat_name == "DeciduousBroadCover") {
727                 height = 15.0;
728                 density = 0.3;
729         }
730         else if(mat_name == "DeciduousForest") {
731                 height = 15.0;
732                 density = 0.3;
733         }
734         else if(mat_name == "MixedForestCover") {
735                 height = 20.0;
736                 density = 0.25;
737         }
738         else if(mat_name == "MixedForest") {
739                 height = 15.0;
740                 density = 0.25;
741         }
742         else if(mat_name == "RainForest") {
743                 height = 25.0;
744                 density = 0.55;
745         }
746         else if(mat_name == "EvergreenNeedleCover") {
747                 height = 15.0;
748                 density = 0.2;
749         }
750         else if(mat_name == "WoodedTundraCover") {
751                 height = 5.0;
752                 density = 0.15;
753         }
754         else if(mat_name == "DeciduousNeedleCover") {
755                 height = 5.0;
756                 density = 0.2;
757         }
758         else if(mat_name == "ScrubCover") {
759                 height = 3.0;
760                 density = 0.15;
761         }
762         else if(mat_name == "BuiltUpCover") {
763                 height = 30.0;
764                 density = 0.7;
765         }
766         else if(mat_name == "Urban") {
767                 height = 30.0;
768                 density = 0.7;
769         }
770         else if(mat_name == "Construction") {
771                 height = 30.0;
772                 density = 0.7;
773         }
774         else if(mat_name == "Industrial") {
775                 height = 30.0;
776                 density = 0.7;
777         }
778         else if(mat_name == "Port") {
779                 height = 30.0;
780                 density = 0.7;
781         }
782         else if(mat_name == "Town") {
783                 height = 10.0;
784                 density = 0.5;
785         }
786         else if(mat_name == "SubUrban") {
787                 height = 10.0;
788                 density = 0.5;
789         }
790         else if(mat_name == "CropWoodCover") {
791                 height = 10.0;
792                 density = 0.1;
793         }
794         else if(mat_name == "CropWood") {
795                 height = 10.0;
796                 density = 0.1;
797         }
798         else if(mat_name == "AgroForest") {
799                 height = 10.0;
800                 density = 0.1;
801         }
802         else {
803                 height = 0.0;
804                 density = 0.0;
805         }
806         
807 }
808
809 /*** implement simple LOS propagation model (WIP)
810 ***/
811 double FGRadioTransmission::LOS_calculate_attenuation(SGGeod pos, double freq, int transmission_type) {
812         double frq_mhz;
813         if( (freq < 118.0) || (freq > 137.0) )
814                 frq_mhz = 125.0;        // sane value, middle of bandplan
815         else
816                 frq_mhz = freq;
817         double dbloss;
818         double tx_pow = _transmitter_power;
819         double ant_gain = _rx_antenna_gain + _tx_antenna_gain;
820         double signal = 0.0;
821         
822         double sender_alt_ft,sender_alt;
823         double transmitter_height=0.0;
824         double receiver_height=0.0;
825         double own_lat = fgGetDouble("/position/latitude-deg");
826         double own_lon = fgGetDouble("/position/longitude-deg");
827         double own_alt_ft = fgGetDouble("/position/altitude-ft");
828         double own_alt= own_alt_ft * SG_FEET_TO_METER;
829         
830         
831         double link_budget = tx_pow - _receiver_sensitivity - _rx_line_losses - _tx_line_losses + ant_gain;     
832
833         //cerr << "ITM:: pilot Lat: " << own_lat << ", Lon: " << own_lon << ", Alt: " << own_alt << endl;
834         
835         SGGeod own_pos = SGGeod::fromDegM( own_lon, own_lat, own_alt );
836         
837         SGGeod sender_pos = pos;
838         
839         sender_alt_ft = sender_pos.getElevationFt();
840         sender_alt = sender_alt_ft * SG_FEET_TO_METER;
841         
842         receiver_height = own_alt;
843         transmitter_height = sender_alt;
844         
845         double distance_m = SGGeodesy::distanceM(own_pos, sender_pos);
846         
847         
848         transmitter_height += _tx_antenna_height;
849         receiver_height += _rx_antenna_height;
850         
851         
852         /** radio horizon calculation with wave bending k=4/3 */
853         double receiver_horizon = 4.12 * sqrt(receiver_height);
854         double transmitter_horizon = 4.12 * sqrt(transmitter_height);
855         double total_horizon = receiver_horizon + transmitter_horizon;
856         
857         if (distance_m > total_horizon) {
858                 return -1;
859         }
860         
861         // free-space loss (distance calculation should be changed)
862         dbloss = 20 * log10(distance_m) +20 * log10(frq_mhz) -27.55;
863         signal = link_budget - dbloss;
864         SG_LOG(SG_GENERAL, SG_BULK,
865                         "LOS:: Link budget: " << link_budget << ", Attenuation: " << dbloss << " dBm ");
866         //cerr << "LOS:: Link budget: " << link_budget << ", Attenuation: " << dbloss << " dBm " << endl;
867         return signal;
868         
869 }
870
871 /*** calculate loss due to polarization mismatch
872 *       this function is only reliable for vertical polarization
873 *       due to the V-shape of horizontally polarized antennas
874 ***/
875 double FGRadioTransmission::polarization_loss() {
876         
877         double theta_deg;
878         double roll = fgGetDouble("/orientation/roll-deg");
879         double pitch = fgGetDouble("/orientation/pitch-deg");
880         double theta = acos( sqrt( cos(roll) * cos(roll) + cos(pitch) * cos(pitch) ));
881         if (_polarization == 1)
882                 theta_deg = 90.0 - theta;
883         else
884                 theta_deg = theta;
885         if (fabs(theta_deg) > 85.0)     // we don't want to converge into infinity
886                 theta_deg = 85.0;
887         return 10 * log10(cos(theta_deg) * cos(theta_deg));
888 }
889
890