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.
101 down rate 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')
166 || $self->ut_numbern('down_rate')
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 process_generate_coverage JOB, PARAMS
260 Queueable routine to fetch the sector coverage map from the tower mapping
261 server and store it. Highly experimental. Requires L<Map::Splat> to be
264 PARAMS must include 'sectornum'.
268 sub process_generate_coverage {
271 $job->update_statustext('0,generating map') if $job;
272 my $sectornum = $param->{sectornum};
273 my $sector = FS::tower_sector->by_key($sectornum)
274 or die "sector $sectornum does not exist";
275 my $tower = $sector->tower;
277 load_class('Map::Splat');
278 # since this is still experimental, put it somewhere we can find later
279 my $workdir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/" .
280 "generate_coverage/sector$sectornum-". time;
282 my $splat = Map::Splat->new(
283 lon => $tower->longitude,
284 lat => $tower->latitude,
285 height => ($sector->height || $tower->height || 0),
286 freq => $sector->freq_mhz,
287 azimuth => $sector->direction,
288 h_width => $sector->width,
289 tilt => $sector->downtilt,
290 v_width => $sector->v_width,
291 max_loss => $sector->margin,
292 min_loss => $sector->margin - 80,
297 my $box = $splat->box;
298 foreach (qw(west east south north)) {
299 $sector->set($_, $box->{$_});
301 $sector->set('image', $splat->mask);
302 # mask returns a PNG where everything below max_loss is solid colored,
303 # and everything above it is transparent. More useful for our purposes.
304 my $error = $sector->replace;
305 die $error if $error;
312 L<FS::tower>, L<FS::Record>, schema.html from the base documentation.