line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package RF::Antenna::Planet::MSI::Format; |
2
|
13
|
|
|
13
|
|
1295978
|
use strict; |
|
13
|
|
|
|
|
166
|
|
|
13
|
|
|
|
|
390
|
|
3
|
13
|
|
|
13
|
|
73
|
use warnings; |
|
13
|
|
|
|
|
28
|
|
|
13
|
|
|
|
|
355
|
|
4
|
13
|
|
|
13
|
|
67
|
use Scalar::Util qw(); |
|
13
|
|
|
|
|
27
|
|
|
13
|
|
|
|
|
290
|
|
5
|
13
|
|
|
13
|
|
6405
|
use Tie::IxHash qw{}; |
|
13
|
|
|
|
|
44915
|
|
|
13
|
|
|
|
|
327
|
|
6
|
13
|
|
|
13
|
|
1736
|
use Path::Class qw{}; |
|
13
|
|
|
|
|
160602
|
|
|
13
|
|
|
|
|
355
|
|
7
|
13
|
|
|
13
|
|
5649
|
use RF::Functions 0.04, qw{dbd_dbi dbi_dbd}; |
|
13
|
|
|
|
|
121701
|
|
|
13
|
|
|
|
|
44499
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.13'; |
10
|
|
|
|
|
|
|
our $PACKAGE = __PACKAGE__; |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 NAME |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
RF::Antenna::Planet::MSI::Format - RF Antenna Pattern File Reader and Writer in Planet MSI Format |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 SYNOPSIS |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
Read from MSI file |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
use RF::Antenna::Planet::MSI::Format; |
21
|
|
|
|
|
|
|
my $antenna = RF::Antenna::Planet::MSI::Format->new; |
22
|
|
|
|
|
|
|
$antenna->read($filename); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Create a blank object, load data from other sources, then write antenna pattern file. |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
my $antenna = RF::Antenna::Planet::MSI::Format->new; |
27
|
|
|
|
|
|
|
$antenna->name("My Name"); |
28
|
|
|
|
|
|
|
$antenna->make("My Make"); |
29
|
|
|
|
|
|
|
my $file = $antenna->write($filename); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 DESCRIPTION |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
This package reads and writes antenna radiation patterns in Planet MSI antenna format. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
Planet is a RF propagation simulation tool initially developed by MSI. Planet was a 2G radio planning tool which has set a standard in the early days of computer aided radio network design. The antenna pattern file and the format which is currently known as the ".msi" format or an msi file has become a standard. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=head1 CONSTRUCTORS |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head2 new |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
Creates a new blank object for creating files or loading data from other sources |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
my $antenna = RF::Antenna::Planet::MSI::Format->new; |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
Creates a new object and loads data from other sources |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
my $antenna = RF::Antenna::Planet::MSI::Format->new( |
48
|
|
|
|
|
|
|
NAME => "My Antenna Name", |
49
|
|
|
|
|
|
|
MAKE => "My Manufacturer Name", |
50
|
|
|
|
|
|
|
FREQUENCY => "2437" || "2437 MHz" || "2.437 GHz", |
51
|
|
|
|
|
|
|
GAIN => "10.0" || "10.0 dBd" || "12.15 dBi", |
52
|
|
|
|
|
|
|
COMMENT => "My Comment", |
53
|
|
|
|
|
|
|
horizontal => [[0.00, 0.96], [1.00, 0.04], ..., [180.00, 31.10], ..., [359.00, 0.04]], |
54
|
|
|
|
|
|
|
vertical => [[0.00, 1.08], [1.00, 0.18], ..., [180.00, 31.23], ..., [359.00, 0.18]], |
55
|
|
|
|
|
|
|
); |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=cut |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub new { |
60
|
21
|
|
|
21
|
1
|
7328
|
my $this = shift; |
61
|
21
|
50
|
|
|
|
107
|
die('Error: new constructor requires key/value pairs') if @_ % 2; |
62
|
21
|
50
|
|
|
|
86
|
my $class = ref($this) ? ref($this) : $this; |
63
|
21
|
|
|
|
|
53
|
my $self = {}; |
64
|
21
|
|
|
|
|
53
|
bless $self, $class; |
65
|
|
|
|
|
|
|
|
66
|
21
|
|
|
|
|
53
|
my @later = (); |
67
|
21
|
|
|
|
|
83
|
while (@_) { #preserve order |
68
|
4
|
|
|
|
|
6
|
my $key = shift; |
69
|
4
|
|
|
|
|
8
|
my $value = shift; |
70
|
4
|
100
|
|
|
|
16
|
if ($key eq 'header') { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
71
|
2
|
100
|
|
|
|
8
|
if (ref($value) eq 'HASH') { |
|
|
50
|
|
|
|
|
|
72
|
1
|
|
|
|
|
6
|
my @order = qw{NAME MAKE FREQUENCY GAIN TILT POLARIZATION COMMENT}; |
73
|
1
|
|
|
|
|
5
|
my %copy = %$value; |
74
|
1
|
|
|
|
|
4
|
foreach my $key (@order) { |
75
|
7
|
100
|
|
|
|
19
|
$self->header($key => delete($copy{$key})) if exists $copy{$key}; |
76
|
|
|
|
|
|
|
} |
77
|
1
|
|
|
|
|
4
|
$self->header(%copy); #appends the rest in hash order |
78
|
|
|
|
|
|
|
} elsif (ref($value) eq 'ARRAY') { |
79
|
1
|
|
|
|
|
4
|
$self->header(@$value); #preserves order |
80
|
|
|
|
|
|
|
} else { |
81
|
0
|
|
|
|
|
0
|
die('Error: header value expected to be either array reference or hash reference'); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
} elsif ($key eq 'horizontal') { |
84
|
0
|
0
|
|
|
|
0
|
die('Error: horizontal value expected to be array reference') unless ref($value) eq 'ARRAY'; |
85
|
0
|
|
|
|
|
0
|
$self->horizontal($value); |
86
|
|
|
|
|
|
|
} elsif ($key eq 'vertical') { |
87
|
0
|
0
|
|
|
|
0
|
die('Error: vertical value expected to be array reference') unless ref($value) eq 'ARRAY'; |
88
|
0
|
|
|
|
|
0
|
$self->vertical($value); |
89
|
|
|
|
|
|
|
} else { |
90
|
2
|
50
|
|
|
|
6
|
die('Error: header key/value pairs must be strings') if ref($value); |
91
|
2
|
|
|
|
|
6
|
push @later, $key, $value; #store for later so that we process header before header keys |
92
|
|
|
|
|
|
|
} |
93
|
|
|
|
|
|
|
} |
94
|
21
|
100
|
|
|
|
68
|
$self->header(@later) if @later; |
95
|
|
|
|
|
|
|
|
96
|
21
|
|
|
|
|
67
|
return $self; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=head2 read |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
Reads an antenna pattern file and parses the data into the object data structure. Returns the object so that the call can be chained. |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
$antenna->read($filename); |
104
|
|
|
|
|
|
|
$antenna->read(\$scalar); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Assumptions: |
107
|
|
|
|
|
|
|
The first line in the MSI file contains the name of the antenna. It appears that some vendors suppress the "NAME" token but we always write the NAME token. |
108
|
|
|
|
|
|
|
The keys can be mixed case but convention appears to be all upper case keys for common keys and lower case keys for vendor extensions. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=cut |
111
|
|
|
|
|
|
|
|
112
|
0
|
|
|
|
|
0
|
sub read { |
113
|
9
|
|
|
9
|
1
|
5570
|
my $self = shift; |
114
|
9
|
|
|
|
|
21
|
my $file = shift; |
115
|
9
|
100
|
|
|
|
50
|
my $blob = ref($file) eq 'SCALAR' ? ${$file} : Path::Class::file($file)->slurp; |
|
7
|
|
|
|
|
38
|
|
116
|
9
|
100
|
|
|
|
1033
|
die(qq{Error: Package: $PACKAGE: Method: read, file: "$file" is empty}) unless length($blob); |
117
|
8
|
|
|
|
|
49
|
$self->{'blob'} = $blob; #store for blob method |
118
|
8
|
|
|
|
|
779
|
my @lines = split(/[\n\r]+/, $blob); |
119
|
8
|
|
|
|
|
88
|
my $loop = 0; |
120
|
8
|
|
|
|
|
36
|
while (1) { |
121
|
34
|
|
|
|
|
72
|
my $line = shift @lines; |
122
|
34
|
|
|
|
|
127
|
$line =~ s/\A\s*//; #ltrim |
123
|
34
|
|
|
|
|
168
|
$line =~ s/\s*\Z//; #rtrim |
124
|
34
|
100
|
|
|
|
90
|
if ($line) { |
125
|
30
|
|
|
|
|
46
|
$loop++; |
126
|
30
|
|
|
|
|
126
|
my ($key, $value) = split /\s+/, $line, 2; #split with limit returns undef value if empty string |
127
|
30
|
100
|
|
|
|
87
|
$value = '' unless defined $value; |
128
|
|
|
|
|
|
|
|
129
|
30
|
100
|
100
|
|
|
125
|
if ($loop == 1 and $key ne 'NAME') { #First line of file is NAME even if NAME token is surpessed |
130
|
2
|
|
|
|
|
10
|
$self->header(NAME => $line); |
131
|
|
|
|
|
|
|
} else { |
132
|
|
|
|
|
|
|
#printf "Key: $key, Value: $value\n"; |
133
|
28
|
100
|
|
|
|
98
|
if (uc($key) eq 'HORIZONTAL') { |
|
|
100
|
|
|
|
|
|
134
|
6
|
|
|
|
|
24
|
$self->_parse_polarization(\@lines, horizontal => $value); |
135
|
|
|
|
|
|
|
} elsif (uc($key) eq 'VERTICAL') { |
136
|
6
|
|
|
|
|
25
|
$self->_parse_polarization(\@lines, vertical => $value); |
137
|
|
|
|
|
|
|
} else { |
138
|
16
|
|
|
|
|
52
|
$self->header($key => $value); |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
} |
142
|
34
|
100
|
|
|
|
97
|
last unless @lines; |
143
|
|
|
|
|
|
|
} |
144
|
8
|
|
|
|
|
47
|
return $self; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub _parse_polarization { |
147
|
12
|
|
|
12
|
|
26
|
my $self = shift; |
148
|
12
|
|
|
|
|
19
|
my $lines = shift; |
149
|
12
|
|
|
|
|
25
|
my $method = shift; |
150
|
12
|
|
|
|
|
25
|
my $value = shift; #string |
151
|
12
|
100
|
|
|
|
47
|
if ($value =~ m/([0-9]+)/) { #support bad data like missing 360 or "360 0" |
152
|
10
|
|
|
|
|
39
|
$value = $1 + 0; #convert string to number |
153
|
|
|
|
|
|
|
} else { |
154
|
2
|
|
|
|
|
4
|
$value = 360; #default |
155
|
|
|
|
|
|
|
} |
156
|
12
|
|
|
|
|
55
|
my @data = map {s/\s+\Z//; s/\A\s+//; [split /\s+/, $_, 2]} splice @$lines, 0, $value; |
|
1808
|
|
|
|
|
3842
|
|
|
1808
|
|
|
|
|
3001
|
|
|
1808
|
|
|
|
|
5727
|
|
157
|
12
|
50
|
|
|
|
141
|
die(sprintf('Error: %s records with %s records returned %s records', uc($method), $value, scalar(@data))) unless scalar(@data) == $value; |
158
|
12
|
|
|
|
|
65
|
$self->$method(\@data); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=head2 read_fromZipMember |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
Reads an antenna pattern file from a zipped archive and parses the data into the object data structure. |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
$antenna->read_fromZipMember($zip_filename, $member_filename); |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=cut |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub read_fromZipMember { |
171
|
1
|
|
|
1
|
1
|
876
|
my $self = shift; |
172
|
1
|
50
|
|
|
|
4
|
my $zip_filename = shift or die('Error: zip filename required'); |
173
|
1
|
50
|
|
|
|
10
|
my $member_filename = shift or die('Error: zip member name requried'); |
174
|
|
|
|
|
|
|
|
175
|
1
|
|
|
|
|
747
|
require Archive::Zip; |
176
|
1
|
|
|
|
|
66365
|
my $zip_archive = Archive::Zip->new; |
177
|
1
|
50
|
|
|
|
54
|
unless ( $zip_archive->read("$zip_filename") == Archive::Zip::AZ_OK() ) {die qq{Error: zip file "$zip_filename" read error}}; |
|
0
|
|
|
|
|
0
|
|
178
|
1
|
50
|
|
|
|
3205
|
my $member = $zip_archive->memberNamed($member_filename) or die(qq{Error: zip file "$zip_filename" could not find member "$member_filename"}); |
179
|
1
|
|
|
|
|
78
|
my $blob = $member->contents; |
180
|
1
|
|
|
|
|
1312
|
return $self->read(\$blob); |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head2 blob |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Returns the data blob that was read by the read($file), read($scalar_ref), or read_fromZipMember($,$) methods. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=cut |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
sub blob { |
190
|
1
|
|
|
1
|
1
|
1140
|
my $self = shift; |
191
|
1
|
|
|
|
|
5
|
return $self->{'blob'}; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=head2 write |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Writes the object's data to an antenna pattern file and returns a Path::Class file object of the written file. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
my $file = $antenna->write($filename); #isa Path::Class::file |
199
|
|
|
|
|
|
|
my $tempfile = $antenna->write; #isa Path::Class::file in temp directory |
200
|
|
|
|
|
|
|
$antenna->write(\$scalar); #returns undef with data writen to the variable |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=cut |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub write { |
205
|
2
|
|
|
2
|
1
|
512
|
my $self = shift; |
206
|
2
|
|
|
|
|
6
|
my $filename = shift; |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
#Open file handle |
209
|
2
|
|
|
|
|
5
|
my $fh; |
210
|
|
|
|
|
|
|
my $file; |
211
|
2
|
50
|
|
|
|
14
|
if (ref($filename) eq 'SCALAR') { |
|
|
0
|
|
|
|
|
|
212
|
2
|
|
|
|
|
6
|
$file = undef; |
213
|
2
|
|
|
2
|
|
108
|
open $fh, '>', $filename; |
|
2
|
|
|
|
|
17
|
|
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
15
|
|
214
|
|
|
|
|
|
|
} elsif (length $filename) { |
215
|
0
|
|
|
|
|
0
|
$file = Path::Class::file($filename); |
216
|
0
|
0
|
|
|
|
0
|
$fh = $file->open('w') or die(qq{Error: Cannot open "$filename" for writing}); |
217
|
|
|
|
|
|
|
} else { |
218
|
0
|
|
|
|
|
0
|
require File::Temp; |
219
|
0
|
|
|
|
|
0
|
my $suffix = $self->file_extension; |
220
|
0
|
|
|
|
|
0
|
($fh, $filename) = File::Temp::tempfile('antenna_pattern_XXXXXXXX', TMPDIR => 1, SUFFIX => $suffix); |
221
|
0
|
|
|
|
|
0
|
$file = Path::Class::file($filename); |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
#Print to file handle |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub _print_fh_key_value { |
227
|
736
|
|
|
736
|
|
1019
|
my $fh = shift; |
228
|
736
|
|
|
|
|
1012
|
my $key = shift; |
229
|
736
|
|
|
|
|
1016
|
my $value = shift; |
230
|
736
|
|
|
|
|
1956
|
print $fh "$key $value\n"; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
2
|
|
|
|
|
1820
|
my $header = $self->header; #isa Tie::IxHash ordered hash |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
##Print NAME as first line |
236
|
2
|
|
|
|
|
14
|
_print_fh_key_value($fh, 'NAME', $header->{'NAME'}); |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
##Print rest of the headers |
239
|
2
|
|
|
|
|
17
|
foreach my $key (keys %$header) { |
240
|
10
|
100
|
|
|
|
139
|
next if $key eq 'NAME'; #written above |
241
|
8
|
|
|
|
|
29
|
my $value = $header->{$key}; |
242
|
8
|
50
|
|
|
|
76
|
_print_fh_key_value($fh, $key, $value) if defined $value; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
sub _print_fh_key_array { |
246
|
4
|
|
|
4
|
|
9
|
my $fh = shift; |
247
|
4
|
|
|
|
|
6
|
my $key = shift; |
248
|
4
|
|
|
|
|
9
|
my $array = shift; |
249
|
4
|
50
|
|
|
|
15
|
if (@$array) { |
250
|
4
|
|
|
|
|
38
|
_print_fh_key_value($fh, $key, scalar(@$array)); |
251
|
4
|
|
|
|
|
27
|
foreach my $row (@$array) { |
252
|
722
|
|
|
|
|
1185
|
my $key = $row->[0]; |
253
|
722
|
|
|
|
|
978
|
my $value = $row->[1]; |
254
|
722
|
|
|
|
|
1117
|
_print_fh_key_value($fh, $key, $value); |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
##Print antenna pattern angle and loss values |
260
|
2
|
|
|
|
|
9
|
foreach my $method (qw{horizontal vertical}) { |
261
|
4
|
|
|
|
|
20
|
my $array = $self->$method; |
262
|
4
|
50
|
|
|
|
18
|
next unless $array; |
263
|
4
|
|
|
|
|
10
|
my $key = uc($method); |
264
|
4
|
|
|
|
|
12
|
_print_fh_key_array($fh, $key, $array); |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
#Close file handle and Return file object |
268
|
2
|
|
|
|
|
8
|
close $fh; |
269
|
2
|
|
|
|
|
13
|
return $file; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=head2 file_extension |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
Sets and returns the file extension to use for write method when called without any parameters. |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
my $suffix = $antenna->file_extension('.ant'); |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
Default: .msi |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
Alternatives: .pla, .pln, .ptn, .txt, .ant |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=cut |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
sub file_extension { |
285
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
286
|
0
|
0
|
|
|
|
0
|
$self->{'file_extension'} = shift if @_; |
287
|
0
|
0
|
|
|
|
0
|
$self->{'file_extension'} = '.msi' unless defined($self->{'write_file_extension'}); |
288
|
0
|
|
|
|
|
0
|
return $self->{'file_extension'}; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=head2 media_type |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
Returns the Media Type (formerly known as MIME Type) for use in Internet applications. |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
Default: application/vnd.planet-antenna-pattern |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=cut |
298
|
|
|
|
|
|
|
|
299
|
0
|
|
|
0
|
1
|
0
|
sub media_type {'application/vnd.planet-antenna-pattern'}; |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=head1 DATA STRUCTURE METHODS |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=head2 header |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
Set header values and returns the header data structure which is a hash reference tied to L to preserve header sort order. |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
Set a key/value pair |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
$antenna->header(COMMENT => "My comment"); #upper case keys are common/reserved whereas mixed/lower case keys are vendor extensions |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Set multiple keys/values with one call |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
$antenna->header(NAME => $myname, MAKE => $mymake); |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Read arbitrary values |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
my $value = $antenna->header->{$key}; |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
Returns ordered list of header keys |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
my @keys = keys %{$antenna->header}; |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
Common Header Keys: NAME MAKE FREQUENCY GAIN TILT POLARIZATION COMMENT |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
=cut |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub header { |
328
|
325
|
|
|
325
|
1
|
1096
|
my $self = shift; |
329
|
325
|
50
|
|
|
|
793
|
die('Error: header method requires key/value pairs') if @_ % 2; |
330
|
325
|
100
|
|
|
|
805
|
unless (defined $self->{'header'}) { |
331
|
19
|
|
|
|
|
46
|
my %data = (); |
332
|
19
|
|
|
|
|
145
|
tie(%data, 'Tie::IxHash'); |
333
|
19
|
|
|
|
|
423
|
$self->{'header'} = \%data; |
334
|
|
|
|
|
|
|
} |
335
|
325
|
|
|
|
|
744
|
while (@_) { |
336
|
86
|
|
|
|
|
195
|
my $key = shift; |
337
|
86
|
|
|
|
|
145
|
my $value = shift; |
338
|
86
|
|
|
|
|
445
|
$self->{'header'}->{$key} = $value; |
339
|
|
|
|
|
|
|
} |
340
|
325
|
|
|
|
|
2489
|
return $self->{'header'}; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=head2 horizontal |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
Sets and returns the horizontal data structure for angles with relative loss values from the specified gain in the header. The data structure is an array reference of array references [[$angle1, $value1], [$angle2, $value2], ...] |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
Conventions: The industry has standardized on using 360 points from 0 to 359 degrees with non-negative loss values. The angle 0 is the boresight with increasing values continuing clockwise (e.g., top-down view). Typically, plots show horizontal patterns with 0 degrees pointing up (i.e., North). This is standard compass convention. |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=cut |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
sub horizontal { |
352
|
1103
|
|
|
1103
|
1
|
367626
|
my $self = shift; |
353
|
1103
|
100
|
|
|
|
2898
|
$self->{'horizontal'} = shift if @_; |
354
|
1103
|
|
|
|
|
5169
|
return $self->{'horizontal'}; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=head2 vertical |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
Sets and returns the vertical data structure for angles with relative loss values from the specified gain in the header. The data structure is an array reference of array references [[$angle1, $value1], [$angle2, $value2], ...] |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
Conventions: The industry has standardized on using 360 points from 0 to 359 degrees with non-negative loss values. The angle 0 is the boresight with increasing values continuing clockwise (e.g., left-side view). The angle 0 is the boresight pointing towards the horizon with increasing values continuing clockwise where 90 degrees is pointing to the ground and 270 is pointing into the sky. Typically, plots show vertical patterns with 0 degrees pointing right (i.e., East). |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
=cut |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
sub vertical { |
366
|
1103
|
|
|
1103
|
1
|
2729
|
my $self = shift; |
367
|
1103
|
100
|
|
|
|
2925
|
$self->{'vertical'} = shift if @_; |
368
|
1103
|
|
|
|
|
5012
|
return $self->{'vertical'}; |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=head1 HELPER METHODS |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
Helper methods are wrappers around the header data structure to aid in usability. |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=head2 name |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
Sets and returns the name of the antenna in the header structure |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
my $name = $antenna->name; |
380
|
|
|
|
|
|
|
$antenna->name("My Antenna Name"); |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
Assumed: Less than about 40 ASCII characters |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=cut |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
sub name { |
387
|
12
|
|
|
12
|
1
|
632
|
my $self = shift; |
388
|
12
|
100
|
|
|
|
59
|
$self->header(NAME => shift) if @_; |
389
|
12
|
|
|
|
|
52
|
return $self->header->{'NAME'}; |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=head2 make |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
Sets and returns the name of the manufacturer in the header structure |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
my $make = $antenna->make; |
397
|
|
|
|
|
|
|
$antenna->make("My Antenna Manufacturer"); |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
Assumed: Less than about 40 ASCII characters |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=cut |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
sub make { |
404
|
3
|
|
|
3
|
1
|
1786
|
my $self = shift; |
405
|
3
|
50
|
|
|
|
12
|
$self->header(MAKE => shift) if @_; |
406
|
3
|
|
|
|
|
7
|
return $self->header->{'MAKE'}; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
=head2 frequency |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
Sets and returns the frequency string as displayed in header structure |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
my $frequency = $antenna->frequency; |
414
|
|
|
|
|
|
|
$antenna->frequency("2450"); #correct format in MHz |
415
|
|
|
|
|
|
|
$antenna->frequency("2450 MHz"); #acceptable format |
416
|
|
|
|
|
|
|
$antenna->frequency("2.45 GHz"); #common format but technically not to spec |
417
|
|
|
|
|
|
|
$antenna->frequency("2450-2550"); #common range format but technically not to spec |
418
|
|
|
|
|
|
|
$antenna->frequency("2.45-2.55 GHz"); #common range format but technically not to spec |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=cut |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub frequency { |
423
|
53
|
|
|
53
|
1
|
3668
|
my $self = shift; |
424
|
53
|
100
|
|
|
|
170
|
$self->header(FREQUENCY => shift) if @_; |
425
|
53
|
|
|
|
|
122
|
return $self->header->{'FREQUENCY'}; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=head2 frequency_mhz, frequency_ghz, frequency_mhz_lower, frequency_mhz_upper, frequency_ghz_lower, frequency_ghz_upper |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
Attempts to read and parse the string header value and return the frequency as a number in the requested unit of measure. |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=cut |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
#supported formats |
435
|
|
|
|
|
|
|
#123.1 => assumed MHz |
436
|
|
|
|
|
|
|
#123.1 MHz |
437
|
|
|
|
|
|
|
#123.1 GHz |
438
|
|
|
|
|
|
|
#123.1 kHz |
439
|
|
|
|
|
|
|
#123.1-124.1 => assumed MHz |
440
|
|
|
|
|
|
|
#123.1-124.1 MHz |
441
|
|
|
|
|
|
|
#123.1-124.1 GHz |
442
|
|
|
|
|
|
|
#123.1-124.1 kHz |
443
|
|
|
|
|
|
|
#123x124 |
444
|
|
|
|
|
|
|
#123x124 MHz |
445
|
|
|
|
|
|
|
#123x124 GHz |
446
|
|
|
|
|
|
|
#123x124 kHz |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
sub frequency_mhz { |
449
|
35
|
|
|
35
|
1
|
7653
|
my $self = shift; |
450
|
35
|
|
|
|
|
85
|
my $string = $self->frequency; |
451
|
35
|
|
|
|
|
295
|
my $number = undef; #return undef if cannot parse |
452
|
|
|
|
|
|
|
|
453
|
35
|
50
|
|
|
|
86
|
if (defined($string)) { |
454
|
|
|
|
|
|
|
|
455
|
35
|
|
|
|
|
52
|
my $upper = undef; |
456
|
35
|
|
|
|
|
61
|
my $lower = undef; |
457
|
|
|
|
|
|
|
|
458
|
35
|
|
|
|
|
52
|
my $scale = 1; #Default: MHz |
459
|
35
|
100
|
|
|
|
198
|
if ($string =~ m/GHz/i) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
460
|
14
|
|
|
|
|
32
|
$scale = 1e3; |
461
|
|
|
|
|
|
|
} elsif ($string =~ m/kHz/i) { |
462
|
5
|
|
|
|
|
15
|
$scale = 1e-3; |
463
|
|
|
|
|
|
|
} elsif ($string =~ m/MHz/i) { |
464
|
2
|
|
|
|
|
4
|
$scale = 1; |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
|
467
|
35
|
100
|
|
|
|
388
|
if (Scalar::Util::looks_like_number($string)) { #entire string looks like a number |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
468
|
11
|
|
|
|
|
32
|
$number = $scale * $string; |
469
|
11
|
|
|
|
|
21
|
$lower = $number; |
470
|
11
|
|
|
|
|
18
|
$upper = $number; |
471
|
|
|
|
|
|
|
} elsif ($string =~ m/([0-9]*\.?[0-9]+)[^0-9.]+([0-9]*\.?[0-9]+)/) { #two non-negative numbers with any separator |
472
|
18
|
|
|
|
|
62
|
$lower = $scale * $1; |
473
|
18
|
|
|
|
|
38
|
$upper = $scale * $2; |
474
|
18
|
|
|
|
|
41
|
$number = ($lower + $upper) / 2; |
475
|
|
|
|
|
|
|
} elsif ($string =~ m/([0-9]*\.?[0-9]+)/) { #one non-negative number |
476
|
6
|
|
|
|
|
23
|
$number = $scale * $1; |
477
|
6
|
|
|
|
|
11
|
$lower = $number; |
478
|
6
|
|
|
|
|
11
|
$upper = $number; |
479
|
|
|
|
|
|
|
} |
480
|
35
|
|
|
|
|
65
|
$self->{'frequency_mhz'} = $number; |
481
|
35
|
|
|
|
|
68
|
$self->{'frequency_mhz_lower'} = $lower; |
482
|
35
|
|
|
|
|
65
|
$self->{'frequency_mhz_upper'} = $upper; |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
} |
485
|
35
|
|
|
|
|
101
|
return $number; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
sub frequency_ghz { |
489
|
9
|
|
|
9
|
1
|
23
|
my $self = shift; |
490
|
9
|
|
|
|
|
26
|
my $mhz = $self->frequency_mhz; |
491
|
9
|
50
|
|
|
|
63
|
return $mhz ? $mhz/1000 : undef; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
sub frequency_mhz_lower { |
495
|
7
|
|
|
7
|
1
|
12
|
my $self = shift; |
496
|
7
|
|
|
|
|
22
|
$self->frequency_mhz; #initialize |
497
|
7
|
|
|
|
|
30
|
return $self->{'frequency_mhz_lower'}; |
498
|
|
|
|
|
|
|
} |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
sub frequency_mhz_upper { |
501
|
7
|
|
|
7
|
1
|
14
|
my $self = shift; |
502
|
7
|
|
|
|
|
21
|
$self->frequency_mhz; #initialize |
503
|
7
|
|
|
|
|
29
|
return $self->{'frequency_mhz_upper'}; |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
sub frequency_ghz_lower { |
507
|
2
|
|
|
2
|
1
|
5
|
my $self = shift; |
508
|
2
|
|
|
|
|
7
|
my $mhz = $self->frequency_mhz_lower; |
509
|
2
|
50
|
|
|
|
12
|
return $mhz ? $mhz/1000 : undef; |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
sub frequency_ghz_upper { |
513
|
2
|
|
|
2
|
1
|
5
|
my $self = shift; |
514
|
2
|
|
|
|
|
5
|
my $mhz = $self->frequency_mhz_upper; |
515
|
2
|
50
|
|
|
|
10
|
return $mhz ? $mhz/1000 : undef; |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=head2 gain |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
Sets and returns the antenna gain string as displayed in file (dBd is the default unit of measure) |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
my $gain = $antenna->gain; |
523
|
|
|
|
|
|
|
$antenna->gain("9.1"); #correct format in dBd |
524
|
|
|
|
|
|
|
$antenna->gain("9.1 dBd"); #correct format in dBd |
525
|
|
|
|
|
|
|
$antenna->gain("9.1 dBi"); #correct format in dBi |
526
|
|
|
|
|
|
|
$antenna->gain("(dBi) 9.1"); #supported format |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=cut |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
sub gain { |
531
|
74
|
|
|
74
|
1
|
10769
|
my $self = shift; |
532
|
74
|
100
|
|
|
|
244
|
$self->header(GAIN => shift) if @_; |
533
|
74
|
|
|
|
|
167
|
return $self->header->{'GAIN'}; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
=head2 gain_dbd, gain_dbi |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
Attempts to read and parse the string header value and return the gain as a number in the requested unit of measure. |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=cut |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
sub gain_dbd { |
543
|
48
|
|
|
48
|
1
|
13012
|
my $self = shift; |
544
|
48
|
|
|
|
|
105
|
my $string = $self->gain; |
545
|
48
|
|
|
|
|
409
|
my $number = undef; |
546
|
48
|
50
|
|
|
|
111
|
if (defined($string)) { |
547
|
|
|
|
|
|
|
|
548
|
48
|
100
|
|
|
|
365
|
if (Scalar::Util::looks_like_number($string)) { #entire string looks like a number |
|
|
50
|
|
|
|
|
|
549
|
12
|
|
|
|
|
36
|
$number = $string + 0; #default: dBd |
550
|
|
|
|
|
|
|
} elsif ($string =~ m/([+-]?[0-9]*\.?[0-9]+)/) { #extract number |
551
|
36
|
|
|
|
|
96
|
my $match = $1; |
552
|
36
|
100
|
|
|
|
167
|
$number = $string =~ m/dBi/i ? dbd_dbi($match) : $match + 0; |
553
|
|
|
|
|
|
|
} |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
} |
556
|
48
|
|
|
|
|
261
|
return $number; |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
sub gain_dbi { |
560
|
24
|
|
|
24
|
1
|
52
|
my $self = shift; |
561
|
24
|
|
|
|
|
58
|
my $dbd = $self->gain_dbd; |
562
|
24
|
50
|
|
|
|
101
|
return defined($dbd) ? dbi_dbd($dbd) : undef; |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
=head2 tilt |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
Antenna tilt string as displayed in file. |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
my $tilt = $antenna->tilt; |
570
|
|
|
|
|
|
|
$antenna->tilt("MECHANICAL"); |
571
|
|
|
|
|
|
|
$antenna->tilt("ELECTRICAL"); |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=cut |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
sub tilt { |
576
|
47
|
|
|
47
|
1
|
1496
|
my $self = shift; |
577
|
47
|
100
|
|
|
|
149
|
$self->header(TILT => shift) if @_; |
578
|
47
|
|
|
|
|
100
|
return $self->header->{'TILT'}; |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=head2 electrical_tilt |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
Antenna electrical_tilt string as displayed in file. |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
my $electrical_tilt = $antenna->electrical_tilt; |
587
|
|
|
|
|
|
|
$antenna->electrical_tilt("4"); #4-degree downtilt |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
=cut |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
sub electrical_tilt { |
592
|
18
|
|
|
18
|
1
|
1118
|
my $self = shift; |
593
|
18
|
100
|
|
|
|
50
|
$self->header(ELECTRICAL_TILT => shift) if @_; |
594
|
18
|
|
|
|
|
37
|
return $self->header->{'ELECTRICAL_TILT'}; |
595
|
|
|
|
|
|
|
} |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
=head2 electrical_tilt_degrees |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
Attempts to read and parse the header and return the electrical down tilt in degrees. |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
my $degrees = $antenna->electrical_tilt_degrees; #isa number |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
Note: I recommend storing electrical downtilt in the TILT and ELECTRICAL_TILT headers like this: |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
TILT ELECTRICAL |
606
|
|
|
|
|
|
|
ELECTRICAL_TILT 4 |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
However, this method attempts to read as many different formats as found in the source files. |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
=cut |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
sub electrical_tilt_degrees { |
613
|
28
|
|
|
28
|
1
|
18259
|
my $self = shift; |
614
|
28
|
|
|
|
|
50
|
my $degrees = undef; |
615
|
28
|
|
|
|
|
65
|
my $tilt = $self->tilt; |
616
|
28
|
100
|
|
|
|
237
|
if (defined $tilt) { |
617
|
27
|
|
|
|
|
90
|
$tilt =~ s/\A\s+//; #ltrim |
618
|
27
|
|
|
|
|
71
|
$tilt =~ s/\s+\Z//; #rtrim |
619
|
27
|
100
|
|
|
|
255
|
if ($tilt =~ m/\A(NONE|MECHANICAL)/i ) { #Spec: |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
620
|
3
|
|
|
|
|
7
|
$degrees = 0; |
621
|
|
|
|
|
|
|
} elsif (Scalar::Util::looks_like_number($tilt) ) { #number (assume electrical tilt) |
622
|
1
|
|
|
|
|
10
|
$degrees = abs($tilt + 0); |
623
|
|
|
|
|
|
|
} elsif ($tilt =~ m/\A-?([0-9]{1,2})[-\s]deg.*ELECTRICAL/i) { #8-Deg Electrical |
624
|
1
|
|
|
|
|
5
|
$degrees = $1 + 0; |
625
|
|
|
|
|
|
|
} elsif ($tilt =~ m/\A-?([0-9]{1,2})[-\s]deg.*E-TILT/i ) { #8-Deg E-Tilt |
626
|
1
|
|
|
|
|
4
|
$degrees = $1 + 0; |
627
|
|
|
|
|
|
|
} elsif ($tilt =~ m/\A([0-9]{1,2})T\Z/i ) { #11T |
628
|
2
|
|
|
|
|
9
|
$degrees = $1 + 0; |
629
|
|
|
|
|
|
|
} elsif ($tilt =~ m/\AT([0-9]{1,2})\Z/i ) { #T11 |
630
|
2
|
|
|
|
|
7
|
$degrees = $1 + 0; |
631
|
|
|
|
|
|
|
} elsif ($tilt =~ m/\AELECTRICAL -?([0-9]{1,2})\b/ ) { #ELECTRICAL 11... |
632
|
2
|
|
|
|
|
9
|
$degrees = $1 + 0; |
633
|
|
|
|
|
|
|
} elsif ($tilt =~ m/\AELECTRICAL\Z/i ) { #Spec: ELECTRICAL |
634
|
11
|
|
100
|
|
|
33
|
my $comment = $self->comment // ''; |
635
|
11
|
|
100
|
|
|
104
|
my $electrical_tilt = $self->electrical_tilt // ''; |
636
|
11
|
|
|
|
|
94
|
$electrical_tilt =~ s/\A\s+//; #ltrim |
637
|
11
|
|
|
|
|
29
|
$electrical_tilt =~ s/\s+\Z//; #rtrim |
638
|
11
|
100
|
|
|
|
60
|
if (Scalar::Util::looks_like_number($electrical_tilt) ) { #Spec: ELECTRICAL_TILT 1.25 |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
639
|
2
|
|
|
|
|
8
|
$degrees = abs($electrical_tilt + 0); |
640
|
|
|
|
|
|
|
} elsif ($electrical_tilt =~ m/\A-?([0-9]{1,2})\b/i ) { #ELECTRICAL_TILT 11 degrees |
641
|
1
|
|
|
|
|
4
|
$degrees = $1 + 0; |
642
|
|
|
|
|
|
|
} elsif ($comment =~ m/ELECTRICAL_TILT\s+-?([0-9]{1,2})\b/i) { #COMMENT ELECTRICAL_TILT 8 | COMMENT ELECTRICAL_TILT 8 degrees |
643
|
2
|
|
|
|
|
7
|
$degrees = $1 + 0; |
644
|
|
|
|
|
|
|
} elsif ($comment =~ m/E-?TILT\s+-?([0-9]{1,2})\b/i ) { #COMMENT E-TILT 8 | COMMENT ETilt -2 deg |
645
|
3
|
|
|
|
|
13
|
$degrees = $1 + 0; |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
} |
648
|
|
|
|
|
|
|
} |
649
|
28
|
|
|
|
|
148
|
return $degrees; |
650
|
|
|
|
|
|
|
} |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
=head2 comment |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
Antenna comment string as displayed in file. |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
my $comment = $antenna->comment; |
657
|
|
|
|
|
|
|
$antenna->comment("My Comment"); |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
=cut |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
sub comment { |
662
|
20
|
|
|
20
|
1
|
2337
|
my $self = shift; |
663
|
20
|
100
|
|
|
|
67
|
$self->header(COMMENT => shift) if @_; |
664
|
20
|
|
|
|
|
44
|
return $self->header->{'COMMENT'}; |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
=head1 SEE ALSO |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
Format Definition: L |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
Antenna Pattern File Library L |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
Format Definition from RCC: L |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
=head1 AUTHOR |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
Michael R. Davis |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
MIT License |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
Copyright (c) 2022 Michael R. Davis |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=cut |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
1; |