1 package FS::tower_sector;
3 use Class::Load qw(load_class);
7 use base qw( FS::Record );
8 use FS::Record qw( qsearch qsearchs );
10 use FS::svc_broadband;
14 FS::tower_sector - Object methods for tower_sector records
20 $record = new FS::tower_sector \%hash;
21 $record = new FS::tower_sector { 'column' => 'value' };
23 $error = $record->insert;
25 $error = $new_record->replace($old_record);
27 $error = $record->delete;
29 $error = $record->check;
33 An FS::tower_sector object represents a tower sector. FS::tower_sector
34 inherits from FS::Record. The following fields are currently supported:
56 The height of this antenna on the tower, measured from ground level. This
57 plus the tower's altitude should equal the height of the antenna above sea
62 The band center frequency in MHz.
66 The antenna beam direction in degrees from north.
70 The -3dB horizontal beamwidth in degrees.
74 The antenna beam elevation in degrees below horizontal.
78 The -3dB vertical beamwidth in degrees.
82 The signal loss margin allowed on the sector, in dB. This is normally
83 transmitter EIRP minus receiver sensitivity.
87 The coverage map, as a PNG.
89 =item west, east, south, north
91 The coordinate boundaries of the coverage map.
101 Creates a new sector. To add the sector to the database, see L<"insert">.
103 Note that this stores the hash reference, not a distinct copy of the hash it
104 points to. You can ask the object for a copy with the I<hash> method.
108 sub table { 'tower_sector'; }
112 Adds this record to the database. If there is an error, returns the error,
113 otherwise returns false.
117 Delete this record from the database.
124 #not the most efficient, not not awful, and its not like deleting a sector
125 # with customers is a common operation
126 return "Can't delete a sector with customers" if $self->svc_broadband;
128 $self->SUPER::delete;
133 Checks all fields to make sure this is a valid sector. If there is
134 an error, returns the error, otherwise returns false. Called by the insert
143 $self->ut_numbern('sectornum')
144 || $self->ut_number('towernum', 'tower', 'towernum')
145 || $self->ut_text('sectorname')
146 || $self->ut_textn('ip_addr')
147 || $self->ut_floatn('height')
148 || $self->ut_numbern('freq_mhz')
149 || $self->ut_numbern('direction')
150 || $self->ut_numbern('width')
151 || $self->ut_numbern('v_width')
152 || $self->ut_numbern('downtilt')
153 || $self->ut_floatn('sector_range')
154 || $self->ut_numbern('margin')
155 || $self->ut_anything('image')
156 || $self->ut_sfloatn('west')
157 || $self->ut_sfloatn('east')
158 || $self->ut_sfloatn('south')
159 || $self->ut_sfloatn('north')
161 return $error if $error;
168 Returns the tower for this sector, as an FS::tower object (see L<FS::tower>).
174 qsearchs('tower', { 'towernum'=>$self->towernum } );
179 Returns a description for this sector including tower name.
185 if ( $self->sectorname eq '_default' ) {
186 $self->tower->towername
189 $self->tower->towername. ' sector '. $self->sectorname
195 Returns the services on this tower sector.
201 qsearch('svc_broadband', { 'sectornum' => $self->sectornum });
204 =item need_fields_for_coverage
206 Returns a list of required fields for the coverage map that aren't yet filled.
210 sub need_fields_for_coverage {
212 my $tower = $self->tower;
215 freq_mhz => 'Frequency',
216 direction => 'Direction',
217 downtilt => 'Downtilt',
218 width => 'Horiz. width',
219 v_width => 'Vert. width',
220 margin => 'Signal margin',
221 latitude => 'Latitude',
222 longitude => 'Longitude',
225 foreach (keys %fields) {
226 if ($self->get($_) eq '' and $tower->get($_) eq '') {
227 push @need, $fields{$_};
233 =item queue_generate_coverage
235 Starts a job to recalculate the coverage map.
239 sub queue_generate_coverage {
241 if ( length($self->image) > 0 ) {
242 foreach (qw(image west south east north)) {
245 my $error = $self->replace;
246 return $error if $error;
248 my $job = FS::queue->new({
249 job => 'FS::tower_sector::process_generate_coverage',
251 $job->insert('_JOB', { sectornum => $self->sectornum});
260 =item process_generate_coverage JOB, PARAMS
262 Queueable routine to fetch the sector coverage map from the tower mapping
263 server and store it. Highly experimental. Requires L<Map::Splat> to be
266 PARAMS must include 'sectornum'.
270 sub process_generate_coverage {
274 $job->update_statustext('0,generating map');
275 my $sectornum = $param->{sectornum};
276 my $sector = FS::tower_sector->by_key($sectornum);
277 my $tower = $sector->tower;
279 load_class('Map::Splat');
280 my $splat = Map::Splat->new(
281 lon => $tower->longitude,
282 lat => $tower->latitude,
283 height => ($sector->height || $tower->height || 0),
284 freq => $sector->freq_mhz,
285 azimuth => $sector->direction,
286 h_width => $sector->width,
287 tilt => $sector->downtilt,
288 v_width => $sector->v_width,
289 max_loss => $sector->margin,
290 min_loss => $sector->margin - 80,
294 my $box = $splat->box;
295 foreach (qw(west east south north)) {
296 $sector->set($_, $box->{$_});
298 $sector->set('image', $splat->mask);
299 # mask returns a PNG where everything below max_loss is solid colored,
300 # and everything above it is transparent. More useful for our purposes.
301 my $error = $sector->replace;
302 die $error if $error;
309 L<FS::tower>, L<FS::Record>, schema.html from the base documentation.