| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package RF::Functions; |
|
2
|
1
|
|
|
1
|
|
150832
|
use strict; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
42
|
|
|
3
|
1
|
|
|
1
|
|
13
|
use warnings; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
61
|
|
|
4
|
1
|
|
|
1
|
|
884
|
use POSIX qw{log10}; |
|
|
1
|
|
|
|
|
12315
|
|
|
|
1
|
|
|
|
|
13
|
|
|
5
|
1
|
|
|
1
|
|
2221
|
use base qw{Exporter}; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
275
|
|
|
6
|
1
|
|
|
1
|
|
1172
|
use Math::Round qw{}; |
|
|
1
|
|
|
|
|
3213
|
|
|
|
1
|
|
|
|
|
1584
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $PACKAGE = __PACKAGE__; |
|
9
|
|
|
|
|
|
|
our $VERSION = '0.06'; |
|
10
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
|
11
|
|
|
|
|
|
|
db_ratio ratio2db |
|
12
|
|
|
|
|
|
|
ratio_db db2ratio |
|
13
|
|
|
|
|
|
|
fsl_hz_m fsl_mhz_km fsl_ghz_km fsl_mhz_mi |
|
14
|
|
|
|
|
|
|
okumura_hata_env_mhz_km_m_m |
|
15
|
|
|
|
|
|
|
dbd_dbi dbi_dbd dbd2dbi dbi2dbd dipole_gain |
|
16
|
|
|
|
|
|
|
distance_fcc |
|
17
|
|
|
|
|
|
|
); |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 NAME |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
RF::Functions - Perl Exporter for Radio Frequency (RF) Functions |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
use RF::Functions qw{db_ratio ratio_db}; |
|
26
|
|
|
|
|
|
|
my $db = db_ratio(2); #~3dB |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
RF::Functions is a lib for common RF function. I plan to add additional functions as I need them. |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=head1 FUNCTIONS |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head2 db_ratio, ratio2db |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
Returns dB given a numerical power ratio. |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $db = db_ratio(2); #+3dB |
|
39
|
|
|
|
|
|
|
my $db = db_ratio(1/2); #-3dB |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=cut |
|
42
|
|
|
|
|
|
|
|
|
43
|
2
|
|
|
2
|
1
|
268748
|
sub db_ratio {10 * log10(shift())}; |
|
44
|
|
|
|
|
|
|
|
|
45
|
2
|
|
|
2
|
1
|
19
|
sub ratio2db {10 * log10(shift())}; |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head2 ratio_db, db2ratio |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Returns power ratio given dB. |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
my $power_ratio = ratio_db(3); #2 |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=cut |
|
54
|
|
|
|
|
|
|
|
|
55
|
2
|
|
|
2
|
1
|
16
|
sub ratio_db {10 ** (shift()/10)}; |
|
56
|
|
|
|
|
|
|
|
|
57
|
2
|
|
|
2
|
1
|
14
|
sub db2ratio {10 ** (shift()/10)}; |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=head2 dbi_dbd, dbd2dbi |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
Returns dBi given dBd. Converts the given antenna gain in dBd to dBi. |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
my $eirp = dbi_dbd($erp); |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=cut |
|
66
|
|
|
|
|
|
|
|
|
67
|
1
|
|
|
1
|
1
|
5
|
sub dbi_dbd {shift() + dipole_gain()}; |
|
68
|
|
|
|
|
|
|
|
|
69
|
1
|
|
|
1
|
1
|
4
|
sub dbd2dbi {shift() + dipole_gain()}; |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head2 dbd_dbi, dbi2dbd |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
Returns dBd given dBi. Converts the given antenna gain in dBi to dBd. |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
my $erp = dbd_dbi($eirp); |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=cut |
|
78
|
|
|
|
|
|
|
|
|
79
|
1
|
|
|
1
|
1
|
5
|
sub dbd_dbi {shift() - dipole_gain()}; |
|
80
|
|
|
|
|
|
|
|
|
81
|
1
|
|
|
1
|
1
|
4
|
sub dbi2dbd {shift() - dipole_gain()}; |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head2 dipole_gain |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Returns the gain of a reference half-wave dipole in dBi. |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
my $dipole_gain = dipole_gain(); #always 2.15 dBi |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=cut |
|
90
|
|
|
|
|
|
|
|
|
91
|
5
|
|
|
5
|
1
|
490
|
sub dipole_gain {2.15}; #FCC 10Log(1.64) ~ 2.15 |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=head2 fsl_hz_m, fsl_mhz_km, fsl_ghz_km, fsl_mhz_mi |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
Return power loss in dB given frequency and distance in the specified units of measure |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
my $free_space_loss = fsl_mhz_km($mhz, $km); #returns dB |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=cut |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub fsl_hz_m { |
|
102
|
1
|
|
|
1
|
1
|
457
|
my ($f, $d) = @_; |
|
103
|
1
|
|
|
|
|
32
|
return _fsl_constant($f, $d, -147.55); |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
sub fsl_mhz_km { |
|
107
|
3
|
|
|
3
|
1
|
1251
|
my ($f, $d) = @_; |
|
108
|
3
|
|
|
|
|
11
|
return _fsl_constant($f, $d, 32.45); |
|
109
|
|
|
|
|
|
|
} |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub fsl_ghz_km { |
|
112
|
1
|
|
|
1
|
1
|
403
|
my ($f, $d) = @_; |
|
113
|
1
|
|
|
|
|
4
|
return _fsl_constant($f, $d, 92.45); |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
sub fsl_mhz_mi { |
|
117
|
1
|
|
|
1
|
1
|
403
|
my ($f, $d) = @_; |
|
118
|
1
|
|
|
|
|
4
|
return _fsl_constant($f, $d, 36.58); #const = 20*log10(4*pi/c) where c = 0.186282397 mi/μs (aka mile * MHz) |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub _fsl_constant { |
|
122
|
7
|
100
|
|
7
|
|
1161
|
my $freq = shift; die("Error: Frequency must be positive number") unless $freq > 0; |
|
|
7
|
|
|
|
|
35
|
|
|
123
|
6
|
100
|
|
|
|
11
|
my $dist = shift; die("Error: Distance must be non-negative number") unless $dist >= 0; |
|
|
6
|
|
|
|
|
26
|
|
|
124
|
5
|
100
|
|
|
|
23
|
my $const = shift or die("Error: Constant required"); |
|
125
|
|
|
|
|
|
|
#Equvalent to 20log($freq) + 20log($dist) + $const for performance |
|
126
|
4
|
|
|
|
|
32
|
return Math::Round::nearest(0.001, 20 * log10($freq * $dist) + $const); |
|
127
|
|
|
|
|
|
|
} |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head2 okumura_hata_env_mhz_km_m_m |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Returns power loss in dB given environment code, frequency, distance, base station height, and mobile station height. |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
my $loss_db = $rf->okumura_hata_env_mhz_km_m_m($environment_code, $frequency_mhz, $distance_km, $height_base_station_m, $height_mobile_station_m); |
|
134
|
|
|
|
|
|
|
my $loss_db = $rf->okumura_hata_env_mhz_km_m_m('d', 902, 4.3, 30, 1.5); |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
Propagation Model Parameters and Limitation: |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
Environment code (d => dense urban area, m => medium urban area, s => suburban area, o => open area) |
|
139
|
|
|
|
|
|
|
Frequency (150 - 1500 MHz) |
|
140
|
|
|
|
|
|
|
Distance between the base and mobile station (1 - 20km) |
|
141
|
|
|
|
|
|
|
Height of the base station antenna (30 - 200m) |
|
142
|
|
|
|
|
|
|
Height of the station antenna (1 - 10m) |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
Implementation based on https://en.wikipedia.org/wiki/Hata_model. |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=cut |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub okumura_hata_env_mhz_km_m_m { |
|
149
|
0
|
|
|
0
|
1
|
0
|
my $env = shift; |
|
150
|
0
|
|
|
|
|
0
|
my $f = shift; |
|
151
|
0
|
|
|
|
|
0
|
my $d = shift; |
|
152
|
0
|
|
|
|
|
0
|
my $hB = shift; |
|
153
|
0
|
|
|
|
|
0
|
my $hM = shift; |
|
154
|
0
|
0
|
|
|
|
0
|
die('Error: Okumura-Hata environment code must be one of: d, m, s, o') unless $env =~ m/\A[dmso]\Z/; |
|
155
|
0
|
0
|
0
|
|
|
0
|
die('Error: Okumura-Hata frequency must be between 150 - 1500 MHz') unless ($f >= 150 and $f <= 1500 ); |
|
156
|
0
|
0
|
0
|
|
|
0
|
die('Error: Okumura-Hata distance must be between 1 and 20 km') unless ($d >= 1 and $d <= 20 ); |
|
157
|
0
|
0
|
0
|
|
|
0
|
die('Error: Okumura-Hata height of base station must be between 30 and 200 meters') unless ($hB >= 30 and $hB <= 200 ); |
|
158
|
0
|
0
|
0
|
|
|
0
|
die('Error: Okumura-Hata height of mobile station must be between 1 and 10 meters') unless ($hM >= 1 and $hM <= 10 ); |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
#urban loss calculation |
|
161
|
|
|
|
|
|
|
|
|
162
|
0
|
|
|
|
|
0
|
my $CH = 0; |
|
163
|
0
|
0
|
|
|
|
0
|
if ($env eq 'd') { |
|
164
|
0
|
0
|
|
|
|
0
|
if ($f <= 200) { #dense urban f <= 200 |
|
165
|
|
|
|
|
|
|
#CH = 8.29 ( log (1.54 hM)) ^ 2 - 1.1 if f ≤ 200 |
|
166
|
0
|
|
|
|
|
0
|
$CH = 8.29 * (log10(1.54 * $hM)) ** 2 - 1.1; |
|
167
|
|
|
|
|
|
|
} else { #dense urban f > 200 |
|
168
|
|
|
|
|
|
|
#CH = 3.2 ( log (11.75 hM)) ^ 2 - 4.97 if f ≥ 400 |
|
169
|
0
|
|
|
|
|
0
|
$CH = 3.2 * (log10(11.75 * $hM)) ** 2 - 4.97; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
} else { |
|
172
|
|
|
|
|
|
|
#medium urban |
|
173
|
|
|
|
|
|
|
#CH = (1.1 log(f) - 0.7) hM - (1.56 log(f) - 0.8) |
|
174
|
0
|
|
|
|
|
0
|
$CH = (1.1 * log10($f) - 0.7) * $hM - (1.56 * log10($f) - 0.8) |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
#Lu = 69.55 + 26.16 log( f) - 13.82 log( hB) - CH + (44.9 - 6.55 log( hB)) log( d) |
|
178
|
0
|
|
|
|
|
0
|
my $Lu = 69.55 + 26.16 * log10($f) - 13.82 * log10($hB) - $CH + (44.9 - 6.55 * log10($hB)) * log10($d); |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
#suburban and open environment correction factor |
|
181
|
0
|
|
|
|
|
0
|
my $CF = 0; |
|
182
|
0
|
0
|
|
|
|
0
|
if ($env eq 's') { |
|
|
|
0
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
# -2 [ log ( f/28)]^2 - 5.4 |
|
184
|
0
|
|
|
|
|
0
|
$CF = -2 * (log10($f/28)) ** 2 - 5.4; |
|
185
|
|
|
|
|
|
|
} elsif ($env eq 'o') { |
|
186
|
|
|
|
|
|
|
# -4.78 [ log ( f)] ^ 2 + 18.33 [ log ( f)] - 40.94 |
|
187
|
0
|
|
|
|
|
0
|
$CF = -4.78 * (log10($f)) ** 2 + 18.33 * (log10($f)) - 40.94; |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
|
|
190
|
0
|
|
|
|
|
0
|
return $Lu + $CF; |
|
191
|
|
|
|
|
|
|
} |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=head2 distance_fcc |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
Returns the unrounded distance between the two reference points in kilometers using the formula in 47 CFR 73.208(c). This formula is valid only for distances not exceeding 475 km (295 miles). |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
my $distance_km = distance_fcc($lat1, $lon1, $lat2, $lon2); #reference points latitude and longitude pair in decimal degrees |
|
198
|
|
|
|
|
|
|
my $distance_fcc = Math::Round::round($distance_km); 47 CFR 73.208(c)(8) |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
The FCC's formula is also defined in 47 CFR 1.958 and discussed on L |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=cut |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
my $RADIANS_PER_DEGREE = CORE::atan2(1, 1)/45; |
|
205
|
|
|
|
|
|
|
|
|
206
|
5
|
|
|
5
|
|
25
|
sub _cos_dd {cos(shift() * $RADIANS_PER_DEGREE)}; |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub distance_fcc { |
|
209
|
1
|
|
|
1
|
1
|
1191
|
my $LAT1_dd = shift; # the coordinates of the first reference point in degree-decimal format. |
|
210
|
1
|
|
|
|
|
3
|
my $LON1_dd = shift; |
|
211
|
1
|
|
|
|
|
2
|
my $LAT2_dd = shift; # the coordinates of the second reference point in degree-decimal format. |
|
212
|
1
|
|
|
|
|
3
|
my $LON2_dd = shift; |
|
213
|
|
|
|
|
|
|
#(2) Calculate the middle latitude between the two reference points by averaging the two latitudes as follows: |
|
214
|
|
|
|
|
|
|
#ML = (LAT1_dd + LAT2_dd) ÷ 2; # the middle latitude in degree-decimal format. |
|
215
|
1
|
|
|
|
|
3
|
my $ML = ($LAT1_dd + $LAT2_dd) / 2; |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
#(3) Calculate the number of kilometers per degree latitude difference for the middle latitude calculated in paragraph (c)(2) as follows: |
|
218
|
|
|
|
|
|
|
#KPDlat = 111.13209 - 0.56605 cos(2ML) + 0.00120 cos(4ML) #the number of kilometers per degree of latitude at a given middle latitude. |
|
219
|
1
|
|
|
|
|
5
|
my $KPD_lat = 111.13209 - 0.56605 * _cos_dd(2 * $ML) + 0.00120 * _cos_dd(4 * $ML); |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
#(4) Calculate the number of kilometers per degree longitude difference for the middle latitude calculated in paragraph (c)(2) as follows: |
|
222
|
|
|
|
|
|
|
#KPDlon = 111.41513 cos(ML) - 0.09455 cos(3ML) + 0.00012 cos(5ML) # the number of kilometers per degree of longitude at a given middle latitude. |
|
223
|
1
|
|
|
|
|
4
|
my $KPD_lon = 111.41513 * _cos_dd($ML) - 0.09455 * _cos_dd(3 * $ML) + 0.00012 * _cos_dd(5 * $ML); |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
#(5) Calculate the North-South distance in kilometers as follows: |
|
226
|
|
|
|
|
|
|
#NS = KPDlat(LAT1_dd-LAT2_dd) # the North-South distance in kilometers. |
|
227
|
1
|
|
|
|
|
4
|
my $NS = $KPD_lat * ($LAT1_dd - $LAT2_dd); |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
#(6) Calculate the East-West distance in kilometers as follows: |
|
230
|
|
|
|
|
|
|
#EW = KPDlon(LON1_dd-LON2_dd) # the East-West distance in kilometers. |
|
231
|
1
|
|
|
|
|
3
|
my $EW = $KPD_lon * ($LON1_dd - $LON2_dd); |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
#(7) Calculate the distance between the two reference points by taking the square root of the sum of the squares of the East-West and North-South distances as follows: |
|
234
|
|
|
|
|
|
|
#DIST = (NS^2 + EW^2)^0.5 # the distance between the two reference points, in kilometers. |
|
235
|
1
|
|
|
|
|
6
|
my $DIST = sqrt($NS**2 + $EW**2); |
|
236
|
|
|
|
|
|
|
|
|
237
|
1
|
50
|
|
|
|
6
|
warn("Package: $PACKAGE, Function: distance_fcc, This formula is valid only for distances not exceeding 475 km (295 miles).\n") if $DIST > 475; |
|
238
|
1
|
|
|
|
|
5
|
return $DIST; |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
L, L |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
L |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
L |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
L |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head1 AUTHOR |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
Michael R. Davis, MRDVT |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
MIT LICENSE |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
Copyright (C) 2023 by Michael R. Davis |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
=cut |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
1; |