map selection of tower site
authorMark Wells <mark@freeside.biz>
Wed, 5 Oct 2016 21:06:32 +0000 (14:06 -0700)
committerMark Wells <mark@freeside.biz>
Wed, 5 Oct 2016 21:06:32 +0000 (14:06 -0700)
httemplate/browse/tower.html
httemplate/docs/license.html
httemplate/edit/tower.html
httemplate/elements/jquery-gmaps-latlon-picker.js [new file with mode: 0644]
httemplate/elements/mapselect.html [new file with mode: 0644]
httemplate/elements/tower_sector.html [deleted file]
httemplate/elements/tr-tower_sectors.html
httemplate/misc/sector_coverage-json.cgi
httemplate/search/tower-map.html

index 16e44c6..555b8a3 100644 (file)
@@ -3,8 +3,10 @@
   'name'        => 'towers',
   'menubar'     => [ 'Add a new tower' =>
                       $p.'edit/tower.html',
-                     'Sector coverage maps' =>
-                      $p.'search/sector.html',
+                     #'Sector coverage maps' =>
+                     # $p.'search/sector.html',
+                     'Tower map' =>
+                      $p.'search/tower-map.html',
                      'Download CSV for towercoverage.com' =>
                       $p.'misc/tower-export.html?format=tc'
                   ],
index e5e3eab..0f0a3c6 100644 (file)
@@ -134,6 +134,10 @@ under the terms of the MIT license.
 Contains the XRegExp library by Steven Levithan, licensed under the terms
 of the MIT license.
 
+<P>
+Contains the Google Maps Longitude and Latitude Picker, by Richard Dancsi,
+licensed under the terms of the MIT license.
+
 <!-- artwork -->
 
 <P>
index dae5d5a..946a140 100644 (file)
@@ -2,6 +2,7 @@
      name_singular => 'tower',
      table         => 'tower',
      viewall_dir   => 'browse',
+     html_init     => include('/elements/mapselect.html'),
      fields        => [ 'towername',
                         { field=>'disabled', type=>'checkbox', value=>'Y', },
                         { field=>'color',    type=>'pickcolor' },
diff --git a/httemplate/elements/jquery-gmaps-latlon-picker.js b/httemplate/elements/jquery-gmaps-latlon-picker.js
new file mode 100644 (file)
index 0000000..b839df7
--- /dev/null
@@ -0,0 +1,254 @@
+/**\r
+ *\r
+ * A JQUERY GOOGLE MAPS LATITUDE AND LONGITUDE LOCATION PICKER\r
+ * version 1.2\r
+ *\r
+ * Supports multiple maps. Works on touchscreen. Easy to customize markup and CSS.\r
+ *\r
+ * To see a live demo, go to:\r
+ * http://www.wimagguc.com/projects/jquery-latitude-longitude-picker-gmaps/\r
+ *\r
+ * by Richard Dancsi\r
+ * http://www.wimagguc.com/\r
+ *\r
+ */\r
+\r
+(function($) {\r
+\r
+// for ie9 doesn't support debug console >>>\r
+if (!window.console) window.console = {};\r
+if (!window.console.log) window.console.log = function () { };\r
+// ^^^\r
+\r
+/* local modification */\r
+window.gMapsLatLonPickerState = {};\r
+\r
+$.fn.gMapsLatLonPicker = (function() {\r
+\r
+       var _self = this;\r
+\r
+       ///////////////////////////////////////////////////////////////////////////////////////////////\r
+       // PARAMETERS (MODIFY THIS PART) //////////////////////////////////////////////////////////////\r
+       _self.params = {\r
+               defLat : 0,\r
+               defLng : 0,\r
+               defZoom : 1,\r
+               queryLocationNameWhenLatLngChanges: true,\r
+               queryElevationWhenLatLngChanges: true,\r
+               mapOptions : {\r
+                       mapTypeId: google.maps.MapTypeId.ROADMAP,\r
+      /* local modification */\r
+                       //mapTypeControl: false,\r
+                       disableDoubleClickZoom: true,\r
+                       zoomControlOptions: true,\r
+                       streetViewControl: false\r
+               },\r
+               strings : {\r
+                       markerText : "Drag this Marker",\r
+                       error_empty_field : "Couldn't find coordinates for this place",\r
+                       error_no_results : "Couldn't find coordinates for this place"\r
+               }\r
+       };\r
+\r
+\r
+       ///////////////////////////////////////////////////////////////////////////////////////////////\r
+       // VARIABLES USED BY THE FUNCTION (DON'T MODIFY THIS PART) ////////////////////////////////////\r
+       _self.vars = {\r
+               ID : null,\r
+               LATLNG : null,\r
+               map : null,\r
+               marker : null,\r
+               geocoder : null\r
+       };\r
+\r
+       ///////////////////////////////////////////////////////////////////////////////////////////////\r
+       // PRIVATE FUNCTIONS FOR MANIPULATING DATA ////////////////////////////////////////////////////\r
+       var setPosition = function(position) {\r
+               _self.vars.marker.setPosition(position);\r
+               _self.vars.map.panTo(position);\r
+\r
+               $(_self.vars.cssID + ".gllpZoom").val( _self.vars.map.getZoom() );\r
+               $(_self.vars.cssID + ".gllpLongitude").val( position.lng() );\r
+               $(_self.vars.cssID + ".gllpLatitude").val( position.lat() );\r
+\r
+               $(_self.vars.cssID).trigger("location_changed", $(_self.vars.cssID));\r
+\r
+               if (_self.params.queryLocationNameWhenLatLngChanges) {\r
+                       getLocationName(position);\r
+               }\r
+               if (_self.params.queryElevationWhenLatLngChanges) {\r
+                       getElevation(position);\r
+               }\r
+       };\r
+\r
+       // for reverse geocoding\r
+       var getLocationName = function(position) {\r
+               var latlng = new google.maps.LatLng(position.lat(), position.lng());\r
+               _self.vars.geocoder.geocode({'latLng': latlng}, function(results, status) {\r
+                       if (status == google.maps.GeocoderStatus.OK && results[1]) {\r
+                               $(_self.vars.cssID + ".gllpLocationName").val(results[1].formatted_address);\r
+                       } else {\r
+                               $(_self.vars.cssID + ".gllpLocationName").val("");\r
+                       }\r
+                       $(_self.vars.cssID).trigger("location_name_changed", $(_self.vars.cssID));\r
+               });\r
+       };\r
+\r
+       // for getting the elevation value for a position\r
+       var getElevation = function(position) {\r
+               var latlng = new google.maps.LatLng(position.lat(), position.lng());\r
+\r
+               var locations = [latlng];\r
+\r
+               var positionalRequest = { 'locations': locations };\r
+\r
+               _self.vars.elevator.getElevationForLocations(positionalRequest, function(results, status) {\r
+                       if (status == google.maps.ElevationStatus.OK) {\r
+                               if (results[0]) {\r
+                                       $(_self.vars.cssID + ".gllpElevation").val( results[0].elevation.toFixed(3));\r
+                               } else {\r
+                                       $(_self.vars.cssID + ".gllpElevation").val("");\r
+                               }\r
+                       } else {\r
+                               $(_self.vars.cssID + ".gllpElevation").val("");\r
+                       }\r
+                       $(_self.vars.cssID).trigger("elevation_changed", $(_self.vars.cssID));\r
+               });\r
+       };\r
+\r
+       // search function\r
+       var performSearch = function(string, silent) {\r
+               if (string == "") {\r
+                       if (!silent) {\r
+                               displayError( _self.params.strings.error_empty_field );\r
+                       }\r
+                       return;\r
+               }\r
+               _self.vars.geocoder.geocode(\r
+                       {"address": string},\r
+                       function(results, status) {\r
+                               if (status == google.maps.GeocoderStatus.OK) {\r
+                                       $(_self.vars.cssID + ".gllpZoom").val(11);\r
+                                       _self.vars.map.setZoom( parseInt($(_self.vars.cssID + ".gllpZoom").val()) );\r
+                                       setPosition( results[0].geometry.location );\r
+                               } else {\r
+                                       if (!silent) {\r
+                                               displayError( _self.params.strings.error_no_results );\r
+                                       }\r
+                               }\r
+                       }\r
+               );\r
+       };\r
+\r
+       // error function\r
+       var displayError = function(message) {\r
+               alert(message);\r
+       };\r
+\r
+       ///////////////////////////////////////////////////////////////////////////////////////////////\r
+       // PUBLIC FUNCTIONS  //////////////////////////////////////////////////////////////////////////\r
+       var publicfunc = {\r
+\r
+               // INITIALIZE MAP ON DIV //////////////////////////////////////////////////////////////////\r
+               init : function(object) {\r
+\r
+                       if ( !$(object).attr("id") ) {\r
+                               if ( $(object).attr("name") ) {\r
+                                       $(object).attr("id", $(object).attr("name") );\r
+                               } else {\r
+                                       $(object).attr("id", "_MAP_" + Math.ceil(Math.random() * 10000) );\r
+                               }\r
+                       }\r
+\r
+                       _self.vars.ID = $(object).attr("id");\r
+                       _self.vars.cssID = "#" + _self.vars.ID + " ";\r
+\r
+                       _self.params.defLat  = $(_self.vars.cssID + ".gllpLatitude").val()  ? $(_self.vars.cssID + ".gllpLatitude").val()               : _self.params.defLat;\r
+                       _self.params.defLng  = $(_self.vars.cssID + ".gllpLongitude").val() ? $(_self.vars.cssID + ".gllpLongitude").val()          : _self.params.defLng;\r
+                       _self.params.defZoom = $(_self.vars.cssID + ".gllpZoom").val()      ? parseInt($(_self.vars.cssID + ".gllpZoom").val()) : _self.params.defZoom;\r
+\r
+                       _self.vars.LATLNG = new google.maps.LatLng(_self.params.defLat, _self.params.defLng);\r
+\r
+                       _self.vars.MAPOPTIONS            = _self.params.mapOptions;\r
+                       _self.vars.MAPOPTIONS.zoom   = _self.params.defZoom;\r
+                       _self.vars.MAPOPTIONS.center = _self.vars.LATLNG;\r
+\r
+                       _self.vars.map = new google.maps.Map($(_self.vars.cssID + ".gllpMap").get(0), _self.vars.MAPOPTIONS);\r
+                       _self.vars.geocoder = new google.maps.Geocoder();\r
+                       _self.vars.elevator = new google.maps.ElevationService();\r
+\r
+                       _self.vars.marker = new google.maps.Marker({\r
+                               position: _self.vars.LATLNG,\r
+                               map: _self.vars.map,\r
+                               title: _self.params.strings.markerText,\r
+                               draggable: true\r
+                       });\r
+\r
+                       // Set position on doubleclick\r
+                       google.maps.event.addListener(_self.vars.map, 'dblclick', function(event) {\r
+                               setPosition(event.latLng);\r
+                       });\r
+\r
+                       // Set position on marker move\r
+                       google.maps.event.addListener(_self.vars.marker, 'dragend', function(event) {\r
+                               setPosition(_self.vars.marker.position);\r
+                       });\r
+\r
+                       // Set zoom feld's value when user changes zoom on the map\r
+                       google.maps.event.addListener(_self.vars.map, 'zoom_changed', function(event) {\r
+                               $(_self.vars.cssID + ".gllpZoom").val( _self.vars.map.getZoom() );\r
+                               $(_self.vars.cssID).trigger("location_changed", $(_self.vars.cssID));\r
+                       });\r
+\r
+                       // Update location and zoom values based on input field's value\r
+                       $(_self.vars.cssID + ".gllpUpdateButton").bind("click", function() {\r
+                               var lat = $(_self.vars.cssID + ".gllpLatitude").val();\r
+                               var lng = $(_self.vars.cssID + ".gllpLongitude").val();\r
+                               var latlng = new google.maps.LatLng(lat, lng);\r
+                               _self.vars.map.setZoom( parseInt( $(_self.vars.cssID + ".gllpZoom").val() ) );\r
+                               setPosition(latlng);\r
+                       });\r
+\r
+                       // Search function by search button\r
+                       $(_self.vars.cssID + ".gllpSearchButton").bind("click", function() {\r
+                               performSearch( $(_self.vars.cssID + ".gllpSearchField").val(), false );\r
+                       });\r
+\r
+                       // Search function by gllp_perform_search listener\r
+                       $(document).bind("gllp_perform_search", function(event, object) {\r
+                               performSearch( $(object).attr('string'), true );\r
+                       });\r
+\r
+                       // Zoom function triggered by gllp_perform_zoom listener\r
+                       $(document).bind("gllp_update_fields", function(event) {\r
+                               var lat = $(_self.vars.cssID + ".gllpLatitude").val();\r
+                               var lng = $(_self.vars.cssID + ".gllpLongitude").val();\r
+                               var latlng = new google.maps.LatLng(lat, lng);\r
+                               _self.vars.map.setZoom( parseInt( $(_self.vars.cssID + ".gllpZoom").val() ) );\r
+                               setPosition(latlng);\r
+                       });\r
+\r
+      /* local modification */\r
+      window.gMapsLatLonPickerState[_self.vars.ID] =\r
+      {\r
+        vars : _self.vars,\r
+        params : _self.params\r
+      };\r
+    } // publicfunc\r
+\r
+       }\r
+\r
+       return publicfunc;\r
+});\r
+\r
+}(jQuery));\r
+\r
+$(document).ready( function() {\r
+       $(".gllpLatlonPicker").each(function() {\r
+               $(document).gMapsLatLonPicker().init( $(this) );\r
+       });\r
+});\r
+\r
+$(document).bind("location_changed", function(event, object) {\r
+       console.log("changed: " + $(object).attr('id') );\r
+});\r
diff --git a/httemplate/elements/mapselect.html b/httemplate/elements/mapselect.html
new file mode 100644 (file)
index 0000000..7d1447f
--- /dev/null
@@ -0,0 +1,72 @@
+<%init>
+my $conf = new FS::Conf;
+my $apikey = $conf->config('google_maps_api_key');
+
+my %opt = @_;
+
+# Currently requires two fields named 'latitude' and 'longitude'.
+# Those should be in the edit form. This widget should NOT be in the
+# edit form (or it will submit a bunch of spurious fields, plus pressing
+# "enter" in the search box will submit the form).
+
+</%init>
+<script src="https://maps.googleapis.com/maps/api/js?v=3&libraries=places&key=<% $apikey %>"></script>
+<script src="<% $fsurl %>elements/jquery-gmaps-latlon-picker.js"></script>
+<style>
+  .gllpLatlonPicker, .gllpMap { width: 600px; height: 600px }
+  #search_location { width: 300px }
+</style>
+<fieldset id="latlonpicker" class="gllpLatlonPicker" style="float: right">
+  <input type="text" id="search_location">
+  <input type="hidden" class="gllpLatitude" id="map_lat">
+  <input type="hidden" class="gllpLongitude" id="map_lon">
+  <input type="hidden" class="gllpElevation" id="map_alt">
+  <input type="hidden" class="gllpZoom" id="map_zoom" value="12">
+  <div class="gllpMap"></div>
+</fieldset>
+<br/>
+
+<script>
+
+$(function() {
+  var container = $('#latlonpicker');
+  var map = gMapsLatLonPickerState['latlonpicker'].vars.map;
+
+  var lat = $('#latitude');
+  var lon = $('#longitude');
+  var alt = $('#altitude');
+  $('#map_lat').val(lat.val());
+  $('#map_lon').val(lon.val());
+  $('#map_alt').val(alt.val());
+  $(document).trigger('gllp_update_fields');
+
+  $(document).on('location_changed', function(ev, obj) {
+    lat.val($('#map_lat').val());
+    lon.val($('#map_lon').val());
+  });
+
+  // requires the Elevation API to be enabled
+  $(document).on('elevation_changed', function(ev, obj) {
+    alt.val($('#map_alt').val());
+  });
+
+  // bypass gllp's search mechanism, use the cooler Places search
+  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();
+    if (places[0]) {
+      $('#map_lat').val( places[0].geometry.location.lat() );
+      $('#map_lon').val( places[0].geometry.location.lng() );
+      $('#map_zoom').val(12);
+      $(document).trigger('gllp_update_fields');
+    }
+  });
+});
+</script>
diff --git a/httemplate/elements/tower_sector.html b/httemplate/elements/tower_sector.html
deleted file mode 100644 (file)
index dacb5ba..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-% unless ( $opt{'js_only'} ) {
-
-  <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>">
-
-  <TABLE>
-    <TR>
-%     foreach my $field ( @fields ) {
-
-        <TD>
-          <INPUT TYPE  = "text"
-                 NAME  = "<%$name%>_<%$field%>"
-                 ID    = "<%$id%>_<%$field%>"
-                 SIZE  = "<% $size{$field} || 15 %>"
-                 VALUE = "<% scalar($cgi->param($name."_$field"))
-                             || $tower_sector->get($field) |h %>"
-                 <% $onchange %>
-          ><BR>
-          <FONT SIZE="-1"><% $label{$field} %></FONT>
-        </TD>
-%     }
-    </TR>
-  </TABLE>
-
-
-% }
-<%init>
-
-my( %opt ) = @_;
-
-my $name = $opt{'element_name'} || $opt{'field'} || 'sectornum';
-my $id = $opt{'id'} || 'sectornum';
-
-my $curr_value = $opt{'curr_value'} || $opt{'value'};
-
-my $onchange = '';
-if ( $opt{'onchange'} ) {
-  $onchange = $opt{'onchange'};
-  $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/;
-  $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack.  all onchange
-                                        #callbacks should act the same
-  $onchange = 'onChange="'. $onchange. '"';
-}
-
-my $tower_sector;
-if ( $curr_value ) {
-  $tower_sector = qsearchs('tower_sector', { 'sectornum' => $curr_value } );
-} else {
-  $tower_sector = new FS::tower_sector {};
-}
-
-my %size = ( 'title' => 12 );
-
-tie my %label, 'Tie::IxHash',
-  'sectorname'   => 'Name',
-  'ip_addr'      => 'IP Address',
-  'height'       => 'Height',
-  'freq_mhz'     => 'Freq. (MHz)',
-  'direction'    => 'Direction', # or a button to set these to 0 for omni
-  'downtilt'     => 'Downtilt',
-  'width'        => 'Horiz. width',
-  'v_width'      => 'Vert. width',
-  'sector_range' => 'Range',
-  'db_high'      => 'High quality margin (dB)',
-  'db_low'       => 'Low quality margin (dB)',
-;
-
-my @fields = keys %label;
-
-</%init>
index 5351bec..4e8f3fb 100644 (file)
@@ -31,7 +31,7 @@ if ( $cgi->param('error') ) {
 my $id = $opt{id} || $opt{field} || 'sectornum';
 
 </%init>
-<& tablebreak-tr-title.html, %opt &>
+<& tablebreak-tr-title.html, value => 'Sectors' &>
 
 <style>
   .ui-tabs-nav a {
@@ -110,11 +110,11 @@ $(function() {
   var tabs = $( '#'+id+'_tabs' ).tabs();
 
   function changedSectorName() {
-    var this_panel = $(this).parent();
+    var this_panel = $(this).closest('div');
     var this_tab = tabs.find('#' + this_panel.prop('id') + '_tab');
     // if this is the last panel, make a new one
     if (this_panel.next().length == 0) {
-      addSector(this_panel);
+      addSector();
     }
     // and update the current tab's text with the sector name
     this_tab.find('a').text($(this).val());
index 9fd08d7..37595f5 100644 (file)
@@ -17,7 +17,7 @@ foreach my $sector (@sectors) {
   my $sectornum = $sector->sectornum;
   my $low = $sector->db_low;
   my $high = $sector->db_high;
-  my $color = $sector->tower->color || 'green';
+  my $color = '#' . ($sector->tower->color || 'ffffff');
   foreach my $coverage ( $sector->sector_coverage ) {
     #note $coverage->geometry is already JSON
     my $level = $coverage->db_loss;
index 914457d..4460db8 100755 (executable)
@@ -4,9 +4,10 @@
   
 <style>
 html { height: 100% }
-#map_canvas { height: 100%;  }
+#map_canvas { margin: 0 auto; height: 100%;  }
 span.is_up { font-weight: bold; color: green }
 span.is_down { font-weight: bold; color: red }
+#search_location { width: 300px }
 </style>
 
 <div id="map_canvas"></div>
@@ -172,8 +173,8 @@ 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);
+  canvas.css('height', window.innerHeight - (canvas.offset().top) - 30);
+  canvas.css('width', window.innerWidth - 30);
 
   map = new google.maps.Map(canvas[0], { zoom: 6 });
 
@@ -194,7 +195,7 @@ var initMap = function() {
         map.fitBounds(places[0].geometry.viewport);
       } else {
         map.setCenter(places[0].geometry.location);
-        map.setZoom(13);
+        map.setZoom(14);
       }
     }
   });
@@ -225,7 +226,6 @@ my @towers = qsearch('tower', {
   'longitude' => { op=>'!=', value=>''},
 });
 my %sectors; # towernum => arrayref
-my @overlays;
 my @towernums;
 
 foreach my $tower (@towers) {
@@ -257,17 +257,6 @@ foreach my $tower (@towers) {
   };
 
   $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