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',
   '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'
                   ],
                      '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.
 
 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>
 <!-- artwork -->
 
 <P>
index dae5d5a..946a140 100644 (file)
@@ -2,6 +2,7 @@
      name_singular => 'tower',
      table         => 'tower',
      viewall_dir   => 'browse',
      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' },
      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>
 my $id = $opt{id} || $opt{field} || 'sectornum';
 
 </%init>
-<& tablebreak-tr-title.html, %opt &>
+<& tablebreak-tr-title.html, value => 'Sectors' &>
 
 <style>
   .ui-tabs-nav a {
 
 <style>
   .ui-tabs-nav a {
@@ -110,11 +110,11 @@ $(function() {
   var tabs = $( '#'+id+'_tabs' ).tabs();
 
   function changedSectorName() {
   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) {
     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());
     }
     // 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 $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;
   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% }
   
 <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 }
 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>
 </style>
 
 <div id="map_canvas"></div>
@@ -172,8 +173,8 @@ var initMap = function() {
   var canvas = $('#map_canvas');
 
   // set window height correctly
   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 });
 
 
   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.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
   'longitude' => { op=>'!=', value=>''},
 });
 my %sectors; # towernum => arrayref
-my @overlays;
 my @towernums;
 
 foreach my $tower (@towers) {
 my @towernums;
 
 foreach my $tower (@towers) {
@@ -257,17 +257,6 @@ foreach my $tower (@towers) {
   };
 
   $sectors{$towernum} = [ $tower->tower_sector ];
   };
 
   $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
 
 
 } # foreach $tower