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