1 package FS::tower_sector;
2 use base qw( FS::Record );
4 use Class::Load qw(load_class);
11 FS::tower_sector - Object methods for tower_sector records
17 $record = new FS::tower_sector \%hash;
18 $record = new FS::tower_sector { 'column' => 'value' };
20 $error = $record->insert;
22 $error = $new_record->replace($old_record);
24 $error = $record->delete;
26 $error = $record->check;
30 An FS::tower_sector object represents a tower sector. FS::tower_sector
31 inherits from FS::Record. The following fields are currently supported:
53 The height of this antenna on the tower, measured from ground level. This
54 plus the tower's altitude should equal the height of the antenna above sea
59 The band center frequency in MHz.
63 The antenna beam direction in degrees from north.
67 The -3dB horizontal beamwidth in degrees.
71 The antenna beam elevation in degrees below horizontal.
75 The -3dB vertical beamwidth in degrees.
79 The signal loss margin allowed on the sector, in dB. This is normally
80 transmitter EIRP minus receiver sensitivity.
84 The coverage map, as a PNG.
86 =item west, east, south, north
88 The coordinate boundaries of the coverage map.
98 Creates a new sector. To add the sector to the database, see L<"insert">.
100 Note that this stores the hash reference, not a distinct copy of the hash it
101 points to. You can ask the object for a copy with the I<hash> method.
105 sub table { 'tower_sector'; }
109 Adds this record to the database. If there is an error, returns the error,
110 otherwise returns false.
114 Delete this record from the database.
121 #not the most efficient, not not awful, and its not like deleting a sector
122 # with customers is a common operation
123 return "Can't delete a sector with customers" if $self->svc_broadband;
125 $self->SUPER::delete;
130 Checks all fields to make sure this is a valid sector. If there is
131 an error, returns the error, otherwise returns false. Called by the insert
140 $self->ut_numbern('sectornum')
141 || $self->ut_number('towernum', 'tower', 'towernum')
142 || $self->ut_text('sectorname')
143 || $self->ut_textn('ip_addr')
144 || $self->ut_floatn('height')
145 || $self->ut_numbern('freq_mhz')
146 || $self->ut_numbern('direction')
147 || $self->ut_numbern('width')
148 || $self->ut_numbern('v_width')
149 || $self->ut_numbern('downtilt')
150 || $self->ut_floatn('sector_range')
151 || $self->ut_numbern('margin')
152 || $self->ut_anything('image')
153 || $self->ut_sfloatn('west')
154 || $self->ut_sfloatn('east')
155 || $self->ut_sfloatn('south')
156 || $self->ut_sfloatn('north')
158 return $error if $error;
165 Returns the tower for this sector, as an FS::tower object (see L<FS::tower>).
169 Returns a description for this sector including tower name.
175 if ( $self->sectorname eq '_default' ) {
176 $self->tower->towername
179 $self->tower->towername. ' sector '. $self->sectorname
185 Returns the services on this tower sector.
187 =item need_fields_for_coverage
189 Returns a list of required fields for the coverage map that aren't yet filled.
193 sub need_fields_for_coverage {
195 my $tower = $self->tower;
198 freq_mhz => 'Frequency',
199 direction => 'Direction',
200 downtilt => 'Downtilt',
201 width => 'Horiz. width',
202 v_width => 'Vert. width',
203 margin => 'Signal margin',
204 latitude => 'Latitude',
205 longitude => 'Longitude',
208 foreach (keys %fields) {
209 if ($self->get($_) eq '' and $tower->get($_) eq '') {
210 push @need, $fields{$_};
216 =item queue_generate_coverage
218 Starts a job to recalculate the coverage map.
222 sub queue_generate_coverage {
224 if ( length($self->image) > 0 ) {
225 foreach (qw(image west south east north)) {
228 my $error = $self->replace;
229 return $error if $error;
231 my $job = FS::queue->new({
232 job => 'FS::tower_sector::process_generate_coverage',
234 $job->insert('_JOB', { sectornum => $self->sectornum});
243 =item process_generate_coverage JOB, PARAMS
245 Queueable routine to fetch the sector coverage map from the tower mapping
246 server and store it. Highly experimental. Requires L<Map::Splat> to be
249 PARAMS must include 'sectornum'.
253 sub process_generate_coverage {
257 $job->update_statustext('0,generating map');
258 my $sectornum = $param->{sectornum};
259 my $sector = FS::tower_sector->by_key($sectornum);
260 my $tower = $sector->tower;
262 load_class('Map::Splat');
263 my $splat = Map::Splat->new(
264 lon => $tower->longitude,
265 lat => $tower->latitude,
266 height => ($sector->height || $tower->height || 0),
267 freq => $sector->freq_mhz,
268 azimuth => $sector->direction,
269 h_width => $sector->width,
270 tilt => $sector->downtilt,
271 v_width => $sector->v_width,
272 max_loss => $sector->margin,
273 min_loss => $sector->margin - 80,
277 my $box = $splat->box;
278 foreach (qw(west east south north)) {
279 $sector->set($_, $box->{$_});
281 $sector->set('image', $splat->mask);
282 # mask returns a PNG where everything below max_loss is solid colored,
283 # and everything above it is transparent. More useful for our purposes.
284 my $error = $sector->replace;
285 die $error if $error;
292 L<FS::tower>, L<FS::Record>, schema.html from the base documentation.