File Coverage

blib/lib/RF/Functions.pm
Criterion Covered Total %
statement 51 73 69.8
branch 7 26 26.9
condition 0 12 0.0
subroutine 21 22 95.4
pod 15 15 100.0
total 94 148 63.5


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;