summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Schema.pm11
-rw-r--r--FS/FS/tower_sector.pm155
-rw-r--r--httemplate/edit/process/tower.html1
-rw-r--r--httemplate/edit/tower.html2
-rw-r--r--httemplate/elements/tower_sector.html5
-rw-r--r--httemplate/misc/sector-create_map.html10
-rw-r--r--httemplate/search/elements/gmap.html19
-rw-r--r--httemplate/search/sector.html85
-rwxr-xr-xhttemplate/search/svc_broadband-map.html16
-rw-r--r--httemplate/view/sector_map-png.cgi8
10 files changed, 300 insertions, 12 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 36e6fdf..725c9de 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -4861,9 +4861,16 @@ sub tables_hashref {
'freq_mhz', 'int', 'NULL', '', '', '',
'direction', 'int', 'NULL', '', '', '',
'width', 'int', 'NULL', '', '', '',
- #downtilt etc? rfpath has profile files for devices/antennas you upload?
'sector_range', 'decimal', 'NULL', '', '', '', #?
- ],
+ 'downtilt', 'decimal', 'NULL', '', '', '',
+ 'v_width', 'int', 'NULL', '', '', '',
+ 'margin', 'decimal', 'NULL', '', '', '',
+ 'image', 'blob', 'NULL', '', '', '',
+ 'west', 'decimal', 'NULL', '10,7', '', '',
+ 'east', 'decimal', 'NULL', '10,7', '', '',
+ 'south', 'decimal', 'NULL', '10,7', '', '',
+ 'north', 'decimal', 'NULL', '10,7', '', '',
+ ],
'primary_key' => 'sectornum',
'unique' => [ [ 'towernum', 'sectorname' ], [ 'ip_addr' ], ],
'index' => [ [ 'towernum' ] ],
diff --git a/FS/FS/tower_sector.pm b/FS/FS/tower_sector.pm
index 4fbd89c..8b4c222 100644
--- a/FS/FS/tower_sector.pm
+++ b/FS/FS/tower_sector.pm
@@ -1,6 +1,9 @@
package FS::tower_sector;
use base qw( FS::Record );
+use Class::Load qw(load_class);
+use Data::Dumper;
+
use strict;
=head1 NAME
@@ -24,7 +27,7 @@ FS::tower_sector - Object methods for tower_sector records
=head1 DESCRIPTION
-An FS::tower_sector object represents an tower sector. FS::tower_sector
+An FS::tower_sector object represents a tower sector. FS::tower_sector
inherits from FS::Record. The following fields are currently supported:
=over 4
@@ -45,6 +48,44 @@ sectorname
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
@@ -84,11 +125,6 @@ sub delete {
$self->SUPER::delete;
}
-=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.
-
=item check
Checks all fields to make sure this is a valid sector. If there is
@@ -109,7 +145,15 @@ sub check {
|| $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;
@@ -140,8 +184,107 @@ sub description {
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
+=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;
+ warn Dumper($param);
+ $job->update_statustext('0,generating map');
+ my $sectornum = $param->{sectornum};
+ my $sector = FS::tower_sector->by_key($sectornum);
+ my $tower = $sector->tower;
+
+ load_class('Map::Splat');
+ 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,
+ );
+ $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
diff --git a/httemplate/edit/process/tower.html b/httemplate/edit/process/tower.html
index 02362db..d14ac56 100644
--- a/httemplate/edit/process/tower.html
+++ b/httemplate/edit/process/tower.html
@@ -4,6 +4,7 @@
process_o2m => { 'table' => 'tower_sector',
'fields' => [qw(
sectorname ip_addr height freq_mhz direction width
+ downtilt v_width margin
sector_range
)],
},
diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html
index fa3838d..f27f6ac 100644
--- a/httemplate/edit/tower.html
+++ b/httemplate/edit/tower.html
@@ -38,7 +38,7 @@ my $m2_error_callback = sub { # reconstruct the list
my ($cgi, $object) = @_;
my @fields = qw(
- sectorname ip_addr height freq_mhz direction width sector_range
+ sectorname ip_addr height freq_mhz direction width tilt v_width margin sector_range
);
map {
diff --git a/httemplate/elements/tower_sector.html b/httemplate/elements/tower_sector.html
index 151d3ba..9871775 100644
--- a/httemplate/elements/tower_sector.html
+++ b/httemplate/elements/tower_sector.html
@@ -56,8 +56,11 @@ tie my %label, 'Tie::IxHash',
'height' => 'Height',
'freq_mhz' => 'Freq. (MHz)',
'direction' => 'Direction', # or a button to set these to 0 for omni
- 'width' => 'Width', #
+ 'downtilt' => 'Downtilt',
+ 'width' => 'Horiz. width',
+ 'v_width' => 'Vert. width',
'sector_range' => 'Range',
+ 'margin' => 'Signal margin (dB)',
;
my @fields = keys %label;
diff --git a/httemplate/misc/sector-create_map.html b/httemplate/misc/sector-create_map.html
new file mode 100644
index 0000000..6af5fdd
--- /dev/null
+++ b/httemplate/misc/sector-create_map.html
@@ -0,0 +1,10 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); # ???
+
+my $server =
+ new FS::UI::Web::JSRPC 'FS::tower_sector::process_generate_coverage', $cgi;
+
+</%init>
diff --git a/httemplate/search/elements/gmap.html b/httemplate/search/elements/gmap.html
index 8b070eb..632a323 100644
--- a/httemplate/search/elements/gmap.html
+++ b/httemplate/search/elements/gmap.html
@@ -1,5 +1,6 @@
<%args>
@features
+@overlays
</%args>
<%doc>
Generic Google Maps front end.
@@ -24,6 +25,14 @@ Generic Google Maps front end.
}
}, # end of feature
],
+ overlays => [
+ { url => 'https://localhost/freeside/view/sector_overlay-png.html?102',
+ west => -130.0,
+ east => -128.0,
+ south => 10.0,
+ north => 12.0,
+ }, # make a ground overlay
+ ],
&>
</%doc>
@@ -54,6 +63,7 @@ body { height: 100%; margin: 0px; padding: 0px }
<script type="text/javascript">
var data_geojson = <% encode_json($tree) %>;
+var data_overlays = <% encode_json(\@overlays) %>;
var baseStyle = {
clickable: true,
@@ -75,6 +85,7 @@ var featureStyle = function(feature) {
};
var map;
+var overlays;
function initMap() {
var canvas = $('#map_canvas');
map = new google.maps.Map(canvas[0], { zoom: 6 });
@@ -116,6 +127,14 @@ function initMap() {
}
}); // addListener()
+
+ data_overlays.forEach(function(x) {
+ var url = x.url;
+ delete x.url;
+ var overlay = new google.maps.GroundOverlay( url, x );
+ overlay.setMap(map);
+ overlays.push(overlay);
+ });
}
$().ready( initMap );
diff --git a/httemplate/search/sector.html b/httemplate/search/sector.html
new file mode 100644
index 0000000..d039632
--- /dev/null
+++ b/httemplate/search/sector.html
@@ -0,0 +1,85 @@
+<& /elements/header.html, {
+ 'title' => 'Sector coverage maps',
+ }
+&>
+<style>
+ a.createmap {
+ font-weight: bold;
+ color: blue;
+ }
+ a.viewmap {
+ font-weight: bold;
+ color: green;
+ }
+</style>
+<table class="grid">
+ <thead>
+ <tr>
+ <th>Tower / sector</th>
+ <th colspan=3>
+ </tr>
+ </thead>
+ <tbody>
+% foreach my $sector (@sectors) {
+% my $sectornum = $sector->sectornum;
+ <tr>
+ <td>
+ <a href="<% $fsurl %>edit/tower.html?<% $sector->towernum |h %>">
+ <% $sector->description |h %>
+ </a>
+ </td>
+
+% my @need_fields = $sector->need_fields_for_coverage;
+% if ( @need_fields ) {
+ <td>Need fields:</td>
+ <td>
+ <% join('<br>', @need_fields) %>
+ </td>
+% } else {
+ <td colspan="2" style="text-align: center">
+% my $text = 'Create map';
+% if ( length($sector->image) > 0 ) {
+% $text = 'Reprocess';
+% }
+ <form name="create_<% $sectornum |h %>">
+ <input type="hidden" name="sectornum" value="<% $sectornum |h %>">
+ <& /elements/progress-init.html,
+ 'create_'.$sectornum,
+ [ 'sectornum' ],
+ $fsurl.'misc/sector-create_map.html',
+ { 'message' => 'Map generated' },
+ "sector$sectornum"
+ &>
+ <a class="createmap" href="#" onclick="sector<% $sectornum %>process()">
+ <% $text %>
+ </a>
+% }
+ </td>
+ <td>
+% if ( length($sector->image) > 0 ) {
+ <a class="viewmap" href="<% $fsurl %>search/svc_broadband-map.html?sectornum=<% $sectornum %>">
+ View map
+ </a>
+% }
+ </td>
+ </tr>
+% } # foreach $sector
+ </tbody>
+</table>
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $query = {
+ table => 'tower_sector',
+ select => 'tower_sector.*,
+ tower.latitude, tower.longitude, tower.color, tower.towername',
+ extra_sql => ' WHERE tower.disabled is null',
+ addl_from => ' JOIN tower USING (towernum)',
+ order_by => ' ORDER BY towername, sectorname',
+};
+
+my @sectors = qsearch($query);
+</%init>
diff --git a/httemplate/search/svc_broadband-map.html b/httemplate/search/svc_broadband-map.html
index 4c660b0..64a7f98 100755
--- a/httemplate/search/svc_broadband-map.html
+++ b/httemplate/search/svc_broadband-map.html
@@ -1,6 +1,6 @@
<& /elements/header.html, 'Broadband Search Results' &>
-<& elements/gmap.html, features => \@features &>
+<& elements/gmap.html, features => \@features, overlays => \@overlays &>
<& /elements/footer.html &>
<%init>
@@ -76,7 +76,6 @@ foreach my $svc_broadband (@rows) {
}
- my $tower = $towers{$towernum};
if ( $tower->latitude and $tower->longitude ) {
push @features,
{
@@ -146,6 +145,19 @@ foreach my $tower (values(%towers)) {
};
}
+my @overlays;
+foreach my $sector (values %sectors) {
+ if ( length($sector->image) > 0 ) {
+ push @overlays,
+ { url => $fsurl.'view/sector_map-png.cgi?' . $sector->sectornum,
+ west => $sector->west,
+ east => $sector->east,
+ south => $sector->south,
+ north => $sector->north,
+ };
+ };
+};
+
</%init>
<%def .svc_broadband>
% my $svc = shift;
diff --git a/httemplate/view/sector_map-png.cgi b/httemplate/view/sector_map-png.cgi
new file mode 100644
index 0000000..7e7e799
--- /dev/null
+++ b/httemplate/view/sector_map-png.cgi
@@ -0,0 +1,8 @@
+<%init>
+my ($sectornum) = $cgi->keywords;
+my $sector = FS::tower_sector->by_key($sectornum);
+if ( $sector and length($sector->image) > 0 ) {
+ http_header('Content-Type', 'image/png');
+ $m->print($sector->image);
+}
+</%init>