1 package FS::tower_sector;
2 use base qw( FS::Record );
4 use Class::Load qw(load_class);
5 use File::Path qw(make_path);
12 FS::tower_sector - Object methods for tower_sector records
18 $record = new FS::tower_sector \%hash;
19 $record = new FS::tower_sector { 'column' => 'value' };
21 $error = $record->insert;
23 $error = $new_record->replace($old_record);
25 $error = $record->delete;
27 $error = $record->check;
31 An FS::tower_sector object represents a tower sector. FS::tower_sector
32 inherits from FS::Record. The following fields are currently supported:
54 The height of this antenna on the tower, measured from ground level. This
55 plus the tower's altitude should equal the height of the antenna above sea
60 The band center frequency in MHz.
64 The antenna beam direction in degrees from north.
68 The -3dB horizontal beamwidth in degrees.
72 The antenna beam elevation in degrees below horizontal.
76 The -3dB vertical beamwidth in degrees.
80 The signal loss margin allowed on the sector, in dB. This is normally
81 transmitter EIRP minus receiver sensitivity.
85 The coverage map, as a PNG.
87 =item west, east, south, north
89 The coordinate boundaries of the coverage map.
97 Up rate limit for sector.
101 down rate limit for sector.
111 Creates a new sector. To add the sector to the database, see L<"insert">.
113 Note that this stores the hash reference, not a distinct copy of the hash it
114 points to. You can ask the object for a copy with the I<hash> method.
118 sub table { 'tower_sector'; }
122 Adds this record to the database. If there is an error, returns the error,
123 otherwise returns false.
127 Delete this record from the database.
134 #not the most efficient, not not awful, and its not like deleting a sector
135 # with customers is a common operation
136 return "Can't delete a sector with customers" if $self->svc_broadband;
138 $self->SUPER::delete;
143 Checks all fields to make sure this is a valid sector. If there is
144 an error, returns the error, otherwise returns false. Called by the insert
153 $self->ut_numbern('sectornum')
154 || $self->ut_number('towernum', 'tower', 'towernum')
155 || $self->ut_text('sectorname')
156 || $self->ut_textn('ip_addr')
157 || $self->ut_floatn('height')
158 || $self->ut_numbern('freq_mhz')
159 || $self->ut_numbern('direction')
160 || $self->ut_numbern('width')
161 || $self->ut_numbern('v_width')
162 || $self->ut_numbern('downtilt')
163 || $self->ut_floatn('sector_range')
164 || $self->ut_numbern('margin')
165 || $self->ut_numbern('up_rate_limit')
166 || $self->ut_numbern('down_rate_limit')
167 || $self->ut_anything('image')
168 || $self->ut_sfloatn('west')
169 || $self->ut_sfloatn('east')
170 || $self->ut_sfloatn('south')
171 || $self->ut_sfloatn('north')
173 return $error if $error;
180 Returns the tower for this sector, as an FS::tower object (see L<FS::tower>).
184 Returns a description for this sector including tower name.
190 if ( $self->sectorname eq '_default' ) {
191 $self->tower->towername
194 $self->tower->towername. ' sector '. $self->sectorname
200 Returns the services on this tower sector.
202 =item need_fields_for_coverage
204 Returns a list of required fields for the coverage map that aren't yet filled.
208 sub need_fields_for_coverage {
210 my $tower = $self->tower;
213 freq_mhz => 'Frequency',
214 direction => 'Direction',
215 downtilt => 'Downtilt',
216 width => 'Horiz. width',
217 v_width => 'Vert. width',
218 margin => 'Signal margin',
219 latitude => 'Latitude',
220 longitude => 'Longitude',
223 foreach (keys %fields) {
224 if ($self->get($_) eq '' and $tower->get($_) eq '') {
225 push @need, $fields{$_};
231 =item queue_generate_coverage
233 Starts a job to recalculate the coverage map.
237 sub queue_generate_coverage {
239 if ( length($self->image) > 0 ) {
240 foreach (qw(image west south east north)) {
243 my $error = $self->replace;
244 return $error if $error;
246 my $job = FS::queue->new({
247 job => 'FS::tower_sector::process_generate_coverage',
249 $job->insert('_JOB', { sectornum => $self->sectornum});
258 =item part_export_svc_broadband
260 Returns all svc_broadband exports.
264 sub part_export_svc_broadband {
265 my $info = $FS::part_export::exports{'svc_broadband'} or return;
266 my @exporttypes = map { dbh->quote($_) } keys %$info or return;
268 'table' => 'part_export',
269 'extra_sql' => 'WHERE exporttype IN(' . join(',', @exporttypes) . ')'
279 =item process_generate_coverage JOB, PARAMS
281 Queueable routine to fetch the sector coverage map from the tower mapping
282 server and store it. Highly experimental. Requires L<Map::Splat> to be
285 PARAMS must include 'sectornum'.
289 sub process_generate_coverage {
292 $job->update_statustext('0,generating map') if $job;
293 my $sectornum = $param->{sectornum};
294 my $sector = FS::tower_sector->by_key($sectornum)
295 or die "sector $sectornum does not exist";
296 my $tower = $sector->tower;
298 load_class('Map::Splat');
299 # since this is still experimental, put it somewhere we can find later
300 my $workdir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/" .
301 "generate_coverage/sector$sectornum-". time;
303 my $splat = Map::Splat->new(
304 lon => $tower->longitude,
305 lat => $tower->latitude,
306 height => ($sector->height || $tower->height || 0),
307 freq => $sector->freq_mhz,
308 azimuth => $sector->direction,
309 h_width => $sector->width,
310 tilt => $sector->downtilt,
311 v_width => $sector->v_width,
312 max_loss => $sector->margin,
313 min_loss => $sector->margin - 80,
318 my $box = $splat->box;
319 foreach (qw(west east south north)) {
320 $sector->set($_, $box->{$_});
322 $sector->set('image', $splat->mask);
323 # mask returns a PNG where everything below max_loss is solid colored,
324 # and everything above it is transparent. More useful for our purposes.
325 my $error = $sector->replace;
326 die $error if $error;
333 L<FS::tower>, L<FS::Record>, schema.html from the base documentation.