tower map view
authorMark Wells <mark@freeside.biz>
Thu, 29 Sep 2016 17:18:37 +0000 (10:18 -0700)
committerMark Wells <mark@freeside.biz>
Thu, 29 Sep 2016 17:18:37 +0000 (10:18 -0700)
httemplate/search/svc_broadband-json.cgi [new file with mode: 0755]
httemplate/search/tower-map.html [new file with mode: 0755]

diff --git a/httemplate/search/svc_broadband-json.cgi b/httemplate/search/svc_broadband-json.cgi
new file mode 100755 (executable)
index 0000000..334e1ef
--- /dev/null
@@ -0,0 +1,108 @@
+<% encode_json({
+  type => 'FeatureCollection',
+  features => \@features
+}) %>
+<%init>
+
+die "access denied" unless
+  $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my @features; # geoJSON structure
+
+# accept all the search logic from svc_broadband.cgi...
+my %search_hash;
+if ( $cgi->param('magic') eq 'unlinked' ) {
+  %search_hash = ( 'unlinked' => 1 );
+} else {
+  foreach (qw( custnum agentnum svcpart cust_fields )) {
+    $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+  }
+  foreach (qw(pkgpart routernum towernum sectornum)) {
+    $search_hash{$_} = [ $cgi->param($_) ] if $cgi->param($_);
+  }
+}
+
+if ( $cgi->param('sortby') =~ /^(\w+)$/ ) {
+  $search_hash{'order_by'} = "ORDER BY $1";
+}
+
+my $sql_query = FS::svc_broadband->search(\%search_hash);
+
+my %routerbyblock = ();
+
+my @rows = qsearch($sql_query);
+my %sectors;
+my %towers;
+my %tower_coord;
+
+foreach my $svc_broadband (@rows) {
+  # don't try to show it if coords aren't set
+  next if !$svc_broadband->latitude || !$svc_broadband->longitude;
+  # coerce coordinates to numbers
+  my @coord = (
+    $svc_broadband->longitude + 0,
+    $svc_broadband->latitude + 0,
+  );
+  push @coord, $svc_broadband->altitude + 0
+    if length($svc_broadband->altitude); # it's optional
+
+  my $svcnum = $svc_broadband->svcnum;
+  my $color = $svc_broadband->addr_status_color;
+
+  push @features,
+  {
+    type      => 'Feature',
+    id        => 'svc_broadband/'.$svcnum,
+    geometry  => {
+      type        => 'Point',
+      coordinates => \@coord,
+    },
+    properties => {
+      #content => include('.svc_broadband', $svc_broadband),
+      url   => $fsurl . 'view/svc_broadband-popup.html?' . $svcnum,
+      style => {
+        icon => {
+          fillColor => $color,
+        },
+      },
+    },
+  };
+  # look up tower location and draw connecting line
+  next if !$svc_broadband->sectornum;
+  my $sector = $sectors{$svc_broadband->sectornum} ||= $svc_broadband->tower_sector;
+  my $towernum = $sector->towernum;
+  my $tower = $towers{$towernum};
+
+  if (!$tower) {
+    $tower = $towers{$towernum} = $sector->tower;
+    $tower_coord{$towernum} =
+      [ $tower->longitude + 0,
+        $tower->latitude + 0,
+        ($tower->altitude || 0) + 0,
+      ];
+
+  }
+
+  if ( $tower->latitude and $tower->longitude ) {
+    push @features,
+    {
+      type => 'Feature',
+      id   => 'svc_broadband/'.$svcnum.'/line',
+      geometry => {
+        type        => 'LineString',
+        coordinates => [ \@coord, $tower_coord{$towernum} ],
+      },
+      properties  => {
+        style       => {
+          visible      => 0,
+          strokeColor  => $color,
+          strokeWeight => 2,
+        },
+      },
+    };
+
+  } # if tower has coords
+} # foreach $svc_broadband
+</%init>
diff --git a/httemplate/search/tower-map.html b/httemplate/search/tower-map.html
new file mode 100755 (executable)
index 0000000..9c3ee98
--- /dev/null
@@ -0,0 +1,259 @@
+<& /elements/header.html, '' &>
+
+<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3&libraries=places&key=<% $apikey %>"></script>
+  
+<style>
+html { height: 100% }
+#map_canvas { height: 100%;  }
+span.is_up { font-weight: bold; color: green }
+span.is_down { font-weight: bold; color: red }
+</style>
+
+<div id="map_canvas"></div>
+<input type="text" id="search_location" style="width: 180px">
+
+
+<script type="text/javascript">
+
+var baseMarkerStyle = {
+  clickable: true,
+  icon: {
+    path: google.maps.SymbolPath.CIRCLE,
+    scale: 4,
+    fillColor: 'black',
+    fillOpacity: 1,
+    strokeColor: 'black',
+    strokeWeight: 1,
+  },
+};
+var featureStyle = function(feature) {
+  return $.extend(true, {}, baseMarkerStyle, feature.getProperty('style'));
+}
+
+var map;
+var infoWindow = new google.maps.InfoWindow; // shared among all users
+
+var clickHandler = function(ev) {
+  var feature = ev.feature;
+  if ( feature.getGeometry().getType() == 'Point' ) {
+    // then pop up an info box with the feature content
+    infoWindow.close();
+    infoWindow.setPosition(feature.getGeometry().get());
+
+    if ( feature.getProperty('content') ) {
+      infoWindow.setContent(feature.getProperty('content'));
+    } else {
+      infoWindow.setContent('');
+    }
+
+    if ( feature.getProperty('url') ) {
+      $.ajax({
+        url: feature.getProperty('url'),
+        success: function(data) {
+          infoWindow.setContent(data);
+        }
+      });
+      infoWindow.open(map);
+    } else {
+      infoWindow.open(map);
+    }
+  }
+};
+
+var zoomLayer = function(layer) {
+  // takes a google.maps.Data object
+  var bounds = new google.maps.LatLngBounds;
+  layer.forEach(function(feature) {
+    var g = feature.getGeometry();
+    if (g.getType() == 'Point') {
+      bounds.extend(g.get());
+    } else if (g.getArray) {
+      g.getArray().forEach(function(point) { bounds.extend(point); });
+    }
+  });
+
+  map.fitBounds(bounds);
+};
+
+// set up the main layer
+var tower_data = new google.maps.Data;
+tower_data.addGeoJson(<% encode_json($tower_data) %>);
+tower_data.setStyle(featureStyle);
+tower_data.addListener('click', clickHandler);
+
+var towernums = <% encode_json(\@towernums) %>;
+var tower_svc_data = {};
+
+var revertLayerStyles = function() {
+  // mostly, just to re-hide all connecting lines when something is hidden
+  for (var t in tower_svc_data) {
+    tower_svc_data[t].revertStyle();
+  }
+};
+
+towernums.forEach(function(towernum) {
+  var layer = new google.maps.Data;
+  tower_svc_data[towernum] = layer;
+  layer.loadGeoJson(
+    '<% $fsurl %>search/svc_broadband-json.cgi?towernum=' + towernum
+  );
+  layer.setStyle(featureStyle);
+  layer.addListener('click', clickHandler);
+  layer.addListener('click', function(ev) { // show connecting line
+    var id = ev.feature.getId();
+    var f_line = layer.getFeatureById(id + '/line');
+    layer.overrideStyle(f_line, { visible: true});
+  });
+});
+
+function show_svc_data(towernum, show) {
+  if (show) {
+    tower_svc_data[towernum].setMap(window.map);
+  } else {
+    tower_svc_data[towernum].setMap(null);
+  }
+};
+
+// toggle visibility of the services
+infoWindow.addListener('domready', function(ev) {
+  var show_services_box = $('input[name=show_services]');
+  var towernum = show_services_box.val();
+  var is_shown = tower_svc_data[towernum].getMap() == map;
+  show_services_box.prop('checked', is_shown);
+  show_services_box.on('click', function(clickev) {
+    show_svc_data(towernum, this.checked);
+  });
+});
+
+infoWindow.addListener('closeclick', revertLayerStyles);
+infoWindow.addListener('position_changed', revertLayerStyles);
+
+var initMap = function() {
+  var canvas = $('#map_canvas');
+
+  // set window height correctly
+  canvas.css('height', window.innerHeight - (canvas.offset().top) - 1);
+  canvas.css('width', window.innerWidth - (canvas.offset().left) * 2);
+
+  map = new google.maps.Map(canvas[0], { zoom: 6 });
+
+  //set up search box
+  var searchbox_input = $('#search_location')[0];
+  var searchbox = new google.maps.places.SearchBox(searchbox_input);
+  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(searchbox_input);
+
+  map.addListener('bounds_changed', function() {
+    searchbox.setBounds(map.getBounds());
+  });
+
+  searchbox.addListener('places_changed', function() {
+    var places = searchbox.getPlaces();
+    // xxx fancy mode: find the nearest tower and estimate signal strength
+    if (places[0]) {
+      if (places[0].geometry.viewport) {
+        map.fitBounds(places[0].geometry.viewport);
+      } else {
+        map.setCenter(places[0].geometry.location);
+        map.setZoom(13);
+      }
+    }
+  });
+
+  // put tower locations on map
+  tower_data.setMap(map);
+  zoomLayer(tower_data);
+};
+
+$().ready(initMap);
+
+</script>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied" unless
+  $FS::CurrentUser::CurrentUser->access_right('List services');
+
+my $conf = new FS::Conf;
+
+my $apikey = $conf->config('google_maps_api_key');
+
+my @features; # geoJSON structure
+
+my @towers = qsearch('tower', {
+  'latitude'  => { op=>'!=', value=>''},
+  'longitude' => { op=>'!=', value=>''},
+});
+my %sectors; # towernum => arrayref
+my @overlays;
+my @towernums;
+
+foreach my $tower (@towers) {
+  my $towernum = $tower->towernum;
+  push @towernums, $towernum;
+  my @coord = (
+    $tower->longitude + 0,
+    $tower->latitude + 0,
+  );
+  push @features,
+  {
+    type      => 'Feature',
+    id        => 'tower/'.$towernum,
+    geometry  => {
+      type        => 'Point',
+      coordinates => \@coord,
+    },
+    properties => {
+      style     => {
+        icon => {
+          path        => undef,
+          url         => $fsurl.'images/antenna-square-21x51.png',
+          anchor      => { x => 10, y => 4 },
+          strokeColor => ($tower->color || 'black'),
+        },
+      },
+      content   => include('.tower', $tower),
+    },
+  };
+
+  $sectors{$towernum} = [ $tower->tower_sector ];
+  foreach my $sector (@{ $sectors{$towernum} }) {
+    if ( length($sector->image) > 0 ) {
+      my $o = {
+        url => $fsurl.'view/sector_map-png.cgi?' . $sector->sectornum
+      };
+      foreach (qw(south north west east)) {
+        $o->{$_} = $sector->get($_) + 0;
+      }
+      push @overlays, $o;
+    };
+  };
+
+} # foreach $tower
+
+my $tower_data = {
+  type => 'FeatureCollection',
+  features => \@features
+};
+
+</%init>
+<%def .tower>
+% my $tower = shift;
+% my $can_edit = $FS::CurrentUser::CurrentUser->access_right('Configuration');
+<H3>
+% if ( $can_edit ) {
+  <a target="_blank" href="<% $fsurl %>edit/tower.html?<% $tower->towernum %>">
+% }
+Tower #<% $tower->towernum %> | <% $tower->towername %>
+% if ( $can_edit ) {
+  </a>
+% }
+</H3>
+% my $count_query = 'SELECT COUNT(*) FROM svc_broadband LEFT JOIN addr_status using (ip_addr) JOIN tower_sector USING (sectornum) WHERE tower_sector.towernum = '.$tower->towernum;
+% my $num_down = FS::Record->scalar_sql("$count_query AND addr_status.up IS NULL AND addr_status._date IS NOT NULL");
+% my $num_up = FS::Record->scalar_sql("$count_query AND addr_status.up IS NOT NULL");
+<input type="checkbox" name="show_services" value="<% $tower->towernum %>">
+<% emt('Show services') %>
+( <% $num_up %> <SPAN CLASS="is_up"><% emt('UP') %></SPAN>
+<% $num_down %> <SPAN CLASS="is_down"><% emt('DOWN') %></SPAN> )
+</%def>