1 package FS::tower_sector;
4 use base qw( FS::Record );
5 use FS::Record qw( dbh qsearch qsearchs );
8 use Class::Load qw(load_class);
9 use File::Path qw(make_path);
11 use Storable qw(thaw);
12 use MIME::Base64 qw(decode_base64);
17 FS::tower_sector - Object methods for tower_sector records
23 $record = new FS::tower_sector \%hash;
24 $record = new FS::tower_sector { 'column' => 'value' };
26 $error = $record->insert;
28 $error = $new_record->replace($old_record);
30 $error = $record->delete;
32 $error = $record->check;
36 An FS::tower_sector object represents a tower sector. FS::tower_sector
37 inherits from FS::Record. The following fields are currently supported:
59 The height of this antenna on the tower, measured from ground level. This
60 plus the tower's altitude should equal the height of the antenna above sea
65 The band center frequency in MHz.
69 The antenna beam direction in degrees from north.
73 The -3dB horizontal beamwidth in degrees.
77 The antenna beam elevation in degrees below horizontal.
81 The -3dB vertical beamwidth in degrees.
85 The signal loss margin allowed on the sector, in dB. This is normally
86 transmitter EIRP minus receiver sensitivity.
90 The coverage map, as a PNG.
92 =item west, east, south, north
94 The coordinate boundaries of the coverage map.
102 Up rate limit for sector.
104 =item down_rate_limit
106 down rate limit for sector.
116 Creates a new sector. To add the sector to the database, see L<"insert">.
118 Note that this stores the hash reference, not a distinct copy of the hash it
119 points to. You can ask the object for a copy with the I<hash> method.
123 sub table { 'tower_sector'; }
127 Adds this record to the database. If there is an error, returns the error,
128 otherwise returns false.
132 Delete this record from the database.
139 #not the most efficient, not not awful, and its not like deleting a sector
140 # with customers is a common operation
141 return "Can't delete a sector with customers" if $self->svc_broadband;
143 $self->SUPER::delete;
148 Checks all fields to make sure this is a valid sector. If there is
149 an error, returns the error, otherwise returns false. Called by the insert
158 $self->ut_numbern('sectornum')
159 || $self->ut_number('towernum', 'tower', 'towernum')
160 || $self->ut_text('sectorname')
161 || $self->ut_textn('ip_addr')
162 || $self->ut_floatn('height')
163 || $self->ut_numbern('freq_mhz')
164 || $self->ut_numbern('direction')
165 || $self->ut_numbern('width')
166 || $self->ut_numbern('v_width')
167 || $self->ut_numbern('downtilt')
168 || $self->ut_floatn('sector_range')
169 || $self->ut_numbern('margin')
170 || $self->ut_numbern('up_rate_limit')
171 || $self->ut_numbern('down_rate_limit')
172 || $self->ut_anything('image')
173 || $self->ut_sfloatn('west')
174 || $self->ut_sfloatn('east')
175 || $self->ut_sfloatn('south')
176 || $self->ut_sfloatn('north')
178 return $error if $error;
185 Returns the tower for this sector, as an FS::tower object (see L<FS::tower>).
191 qsearchs('tower', { 'towernum'=>$self->towernum } );
196 Returns a description for this sector including tower name.
202 if ( $self->sectorname eq '_default' ) {
203 $self->tower->towername
206 $self->tower->towername. ' sector '. $self->sectorname
212 Returns the services on this tower sector.
218 qsearch('svc_broadband', { 'sectornum' => $self->sectornum });
221 =item need_fields_for_coverage
223 Returns a list of required fields for the coverage map that aren't yet filled.
227 sub need_fields_for_coverage {
229 my $tower = $self->tower;
232 freq_mhz => 'Frequency',
233 direction => 'Direction',
234 downtilt => 'Downtilt',
235 width => 'Horiz. width',
236 v_width => 'Vert. width',
237 margin => 'Signal margin',
238 latitude => 'Latitude',
239 longitude => 'Longitude',
242 foreach (keys %fields) {
243 if ($self->get($_) eq '' and $tower->get($_) eq '') {
244 push @need, $fields{$_};
250 =item queue_generate_coverage
252 Starts a job to recalculate the coverage map.
256 sub queue_generate_coverage {
258 if ( length($self->image) > 0 ) {
259 foreach (qw(image west south east north)) {
262 my $error = $self->replace;
263 return $error if $error;
265 my $job = FS::queue->new({
266 job => 'FS::tower_sector::process_generate_coverage',
268 $job->insert('_JOB', { sectornum => $self->sectornum});
277 =item part_export_svc_broadband
279 Returns all svc_broadband exports.
283 sub part_export_svc_broadband {
284 my $info = $FS::part_export::exports{'svc_broadband'} or return;
285 my @exporttypes = map { dbh->quote($_) } keys %$info or return;
287 'table' => 'part_export',
288 'extra_sql' => 'WHERE exporttype IN(' . join(',', @exporttypes) . ')'
296 =item process_generate_coverage JOB, PARAMS
298 Queueable routine to fetch the sector coverage map from the tower mapping
299 server and store it. Highly experimental. Requires L<Map::Splat> to be
302 PARAMS must include 'sectornum'.
306 sub process_generate_coverage {
310 $param = thaw(decode_base64($param));
312 $job->update_statustext('0,generating map') if $job;
313 my $sectornum = $param->{sectornum};
314 my $sector = FS::tower_sector->by_key($sectornum)
315 or die "sector $sectornum does not exist";
316 my $tower = $sector->tower;
318 load_class('Map::Splat');
319 # since this is still experimental, put it somewhere we can find later
320 my $workdir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/" .
321 "generate_coverage/sector$sectornum-". time;
323 my $splat = Map::Splat->new(
324 lon => $tower->longitude,
325 lat => $tower->latitude,
326 height => ($sector->height || $tower->height || 0),
327 freq => $sector->freq_mhz,
328 azimuth => $sector->direction,
329 h_width => $sector->width,
330 tilt => $sector->downtilt,
331 v_width => $sector->v_width,
332 max_loss => $sector->margin,
333 min_loss => $sector->margin - 80,
338 my $box = $splat->box;
339 foreach (qw(west east south north)) {
340 $sector->set($_, $box->{$_});
342 $sector->set('image', $splat->mask);
343 # mask returns a PNG where everything below max_loss is solid colored,
344 # and everything above it is transparent. More useful for our purposes.
345 my $error = $sector->replace;
346 die $error if $error;
353 L<FS::tower>, L<FS::Record>, schema.html from the base documentation.