add email delivery of saved searches, #72101
[freeside.git] / FS / FS / tower_sector.pm
index 80e7f51..3fadc86 100644 (file)
@@ -1,9 +1,11 @@
 package FS::tower_sector;
 package FS::tower_sector;
+use base qw( FS::Record );
+
+use Class::Load qw(load_class);
+use File::Path qw(make_path);
+use Data::Dumper;
 
 use strict;
 
 use strict;
-use base qw( FS::Record );
-use FS::Record qw( qsearchs ); # qsearch );
-use FS::tower;
 
 =head1 NAME
 
 
 =head1 NAME
 
@@ -26,8 +28,8 @@ FS::tower_sector - Object methods for tower_sector records
 
 =head1 DESCRIPTION
 
 
 =head1 DESCRIPTION
 
-An FS::tower_sector object represents an example.  FS::tower_sector inherits from
-FS::Record.  The following fields are currently supported:
+An FS::tower_sector object represents a tower sector.  FS::tower_sector
+inherits from FS::Record.  The following fields are currently supported:
 
 =over 4
 
 
 =over 4
 
@@ -47,6 +49,44 @@ sectorname
 
 ip_addr
 
 
 ip_addr
 
+=item height
+
+The height of this antenna on the tower, measured from ground level. This
+plus the tower's altitude should equal the height of the antenna above sea
+level.
+
+=item freq_mhz
+
+The band center frequency in MHz.
+
+=item direction
+
+The antenna beam direction in degrees from north.
+
+=item width
+
+The -3dB horizontal beamwidth in degrees.
+
+=item downtilt
+
+The antenna beam elevation in degrees below horizontal.
+
+=item v_width
+
+The -3dB vertical beamwidth in degrees.
+
+=item margin
+
+The signal loss margin allowed on the sector, in dB. This is normally
+transmitter EIRP minus receiver sensitivity.
+
+=item image 
+
+The coverage map, as a PNG.
+
+=item west, east, south, north
+
+The coordinate boundaries of the coverage map.
 
 =back
 
 
 =back
 
@@ -56,15 +96,13 @@ ip_addr
 
 =item new HASHREF
 
 
 =item new HASHREF
 
-Creates a new example.  To add the example to the database, see L<"insert">.
+Creates a new sector.  To add the sector to the database, see L<"insert">.
 
 Note that this stores the hash reference, not a distinct copy of the hash it
 points to.  You can ask the object for a copy with the I<hash> method.
 
 =cut
 
 
 Note that this stores the hash reference, not a distinct copy of the hash it
 points to.  You can ask the object for a copy with the I<hash> method.
 
 =cut
 
-# the new method can be inherited from FS::Record, if a table method is defined
-
 sub table { 'tower_sector'; }
 
 =item insert
 sub table { 'tower_sector'; }
 
 =item insert
@@ -72,38 +110,30 @@ sub table { 'tower_sector'; }
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
-=cut
-
-# the insert method can be inherited from FS::Record
-
 =item delete
 
 Delete this record from the database.
 
 =cut
 
 =item delete
 
 Delete this record from the database.
 
 =cut
 
-# the delete method can be inherited from FS::Record
-
-=item replace OLD_RECORD
-
-Replaces the OLD_RECORD with this one in the database.  If there is an error,
-returns the error, otherwise returns false.
+sub delete {
+  my $self = shift;
 
 
-=cut
+  #not the most efficient, not not awful, and its not like deleting a sector
+  # with customers is a common operation
+  return "Can't delete a sector with customers" if $self->svc_broadband;
 
 
-# the replace method can be inherited from FS::Record
+  $self->SUPER::delete;
+}
 
 =item check
 
 
 =item check
 
-Checks all fields to make sure this is a valid example.  If there is
+Checks all fields to make sure this is a valid sector.  If there is
 an error, returns the error, otherwise returns false.  Called by the insert
 and replace methods.
 
 =cut
 
 an error, returns the error, otherwise returns false.  Called by the insert
 and replace methods.
 
 =cut
 
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
 sub check {
   my $self = shift;
 
 sub check {
   my $self = shift;
 
@@ -112,6 +142,19 @@ sub check {
     || $self->ut_number('towernum', 'tower', 'towernum')
     || $self->ut_text('sectorname')
     || $self->ut_textn('ip_addr')
     || $self->ut_number('towernum', 'tower', 'towernum')
     || $self->ut_text('sectorname')
     || $self->ut_textn('ip_addr')
+    || $self->ut_floatn('height')
+    || $self->ut_numbern('freq_mhz')
+    || $self->ut_numbern('direction')
+    || $self->ut_numbern('width')
+    || $self->ut_numbern('v_width')
+    || $self->ut_numbern('downtilt')
+    || $self->ut_floatn('sector_range')
+    || $self->ut_numbern('margin')
+    || $self->ut_anything('image')
+    || $self->ut_sfloatn('west')
+    || $self->ut_sfloatn('east')
+    || $self->ut_sfloatn('south')
+    || $self->ut_sfloatn('north')
   ;
   return $error if $error;
 
   ;
   return $error if $error;
 
@@ -122,13 +165,6 @@ sub check {
 
 Returns the tower for this sector, as an FS::tower object (see L<FS::tower>).
 
 
 Returns the tower for this sector, as an FS::tower object (see L<FS::tower>).
 
-=cut
-
-sub tower {
-  my $self = shift;
-  qsearchs('tower', { 'towernum'=>$self->towernum } );
-}
-
 =item description
 
 Returns a description for this sector including tower name.
 =item description
 
 Returns a description for this sector including tower name.
@@ -145,8 +181,116 @@ sub description {
   }
 }
 
   }
 }
 
+=item svc_broadband
+
+Returns the services on this tower sector.
+
+=item need_fields_for_coverage
+
+Returns a list of required fields for the coverage map that aren't yet filled.
+
+=cut
+
+sub need_fields_for_coverage {
+  my $self = shift;
+  my $tower = $self->tower;
+  my %fields = (
+    height    => 'Height',
+    freq_mhz  => 'Frequency',
+    direction => 'Direction',
+    downtilt  => 'Downtilt',
+    width     => 'Horiz. width',
+    v_width   => 'Vert. width',
+    margin    => 'Signal margin',
+    latitude  => 'Latitude',
+    longitude => 'Longitude',
+  );
+  my @need;
+  foreach (keys %fields) {
+    if ($self->get($_) eq '' and $tower->get($_) eq '') {
+      push @need, $fields{$_};
+    }
+  }
+  @need;
+}
+
+=item queue_generate_coverage
+
+Starts a job to recalculate the coverage map.
+
+=cut
+
+sub queue_generate_coverage {
+  my $self = shift;
+  if ( length($self->image) > 0 ) {
+    foreach (qw(image west south east north)) {
+      $self->set($_, '');
+    }
+    my $error = $self->replace;
+    return $error if $error;
+  }
+  my $job = FS::queue->new({
+      job => 'FS::tower_sector::process_generate_coverage',
+  });
+  $job->insert('_JOB', { sectornum => $self->sectornum});
+}
+
 =back
 
 =back
 
+=head1 SUBROUTINES
+
+=over 4
+
+=item process_generate_coverage JOB, PARAMS
+
+Queueable routine to fetch the sector coverage map from the tower mapping
+server and store it. Highly experimental. Requires L<Map::Splat> to be
+installed.
+
+PARAMS must include 'sectornum'.
+
+=cut
+
+sub process_generate_coverage {
+  my $job = shift;
+  my $param = shift;
+  $job->update_statustext('0,generating map') if $job;
+  my $sectornum = $param->{sectornum};
+  my $sector = FS::tower_sector->by_key($sectornum)
+    or die "sector $sectornum does not exist";
+  my $tower = $sector->tower;
+
+  load_class('Map::Splat');
+  # since this is still experimental, put it somewhere we can find later
+  my $workdir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/" .
+                "generate_coverage/sector$sectornum-". time;
+  make_path($workdir);
+  my $splat = Map::Splat->new(
+    lon         => $tower->longitude,
+    lat         => $tower->latitude,
+    height      => ($sector->height || $tower->height || 0),
+    freq        => $sector->freq_mhz,
+    azimuth     => $sector->direction,
+    h_width     => $sector->width,
+    tilt        => $sector->downtilt,
+    v_width     => $sector->v_width,
+    max_loss    => $sector->margin,
+    min_loss    => $sector->margin - 80,
+    dir         => $workdir,
+  );
+  $splat->calculate;
+
+  my $box = $splat->box;
+  foreach (qw(west east south north)) {
+    $sector->set($_, $box->{$_});
+  }
+  $sector->set('image', $splat->mask);
+  # mask returns a PNG where everything below max_loss is solid colored,
+  # and everything above it is transparent. More useful for our purposes.
+  my $error = $sector->replace;
+  die $error if $error;
+}
+
 =head1 BUGS
 
 =head1 SEE ALSO
 =head1 BUGS
 
 =head1 SEE ALSO