diff options
author | Mark Wells <mark@freeside.biz> | 2016-10-05 14:06:32 -0700 |
---|---|---|
committer | Mark Wells <mark@freeside.biz> | 2016-10-05 14:06:32 -0700 |
commit | 07a1441c0fbce0a6ec76c7821e440444b4aec4f8 (patch) | |
tree | c368243803fc6b22db7004204204fce3241669da | |
parent | 4c072dbb00979f34b9855b792253b612cc8b226c (diff) |
map selection of tower site
-rw-r--r-- | httemplate/browse/tower.html | 6 | ||||
-rw-r--r-- | httemplate/docs/license.html | 4 | ||||
-rw-r--r-- | httemplate/edit/tower.html | 1 | ||||
-rw-r--r-- | httemplate/elements/jquery-gmaps-latlon-picker.js | 254 | ||||
-rw-r--r-- | httemplate/elements/mapselect.html | 72 | ||||
-rw-r--r-- | httemplate/elements/tower_sector.html | 69 | ||||
-rw-r--r-- | httemplate/elements/tr-tower_sectors.html | 6 | ||||
-rw-r--r-- | httemplate/misc/sector_coverage-json.cgi | 2 | ||||
-rwxr-xr-x | httemplate/search/tower-map.html | 21 |
9 files changed, 344 insertions, 91 deletions
diff --git a/httemplate/browse/tower.html b/httemplate/browse/tower.html index 16e44c6c0..555b8a39f 100644 --- a/httemplate/browse/tower.html +++ b/httemplate/browse/tower.html @@ -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' ], diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html index e5e3eab65..0f0a3c61d 100644 --- a/httemplate/docs/license.html +++ b/httemplate/docs/license.html @@ -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> diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html index dae5d5a89..946a1405e 100644 --- a/httemplate/edit/tower.html +++ b/httemplate/edit/tower.html @@ -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 index 000000000..b839df75a --- /dev/null +++ b/httemplate/elements/jquery-gmaps-latlon-picker.js @@ -0,0 +1,254 @@ +/**
+ *
+ * A JQUERY GOOGLE MAPS LATITUDE AND LONGITUDE LOCATION PICKER
+ * version 1.2
+ *
+ * Supports multiple maps. Works on touchscreen. Easy to customize markup and CSS.
+ *
+ * To see a live demo, go to:
+ * http://www.wimagguc.com/projects/jquery-latitude-longitude-picker-gmaps/
+ *
+ * by Richard Dancsi
+ * http://www.wimagguc.com/
+ *
+ */
+
+(function($) {
+
+// for ie9 doesn't support debug console >>>
+if (!window.console) window.console = {};
+if (!window.console.log) window.console.log = function () { };
+// ^^^
+
+/* local modification */
+window.gMapsLatLonPickerState = {};
+
+$.fn.gMapsLatLonPicker = (function() {
+
+ var _self = this;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PARAMETERS (MODIFY THIS PART) //////////////////////////////////////////////////////////////
+ _self.params = {
+ defLat : 0,
+ defLng : 0,
+ defZoom : 1,
+ queryLocationNameWhenLatLngChanges: true,
+ queryElevationWhenLatLngChanges: true,
+ mapOptions : {
+ mapTypeId: google.maps.MapTypeId.ROADMAP,
+ /* local modification */
+ //mapTypeControl: false,
+ disableDoubleClickZoom: true,
+ zoomControlOptions: true,
+ streetViewControl: false
+ },
+ strings : {
+ markerText : "Drag this Marker",
+ error_empty_field : "Couldn't find coordinates for this place",
+ error_no_results : "Couldn't find coordinates for this place"
+ }
+ };
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // VARIABLES USED BY THE FUNCTION (DON'T MODIFY THIS PART) ////////////////////////////////////
+ _self.vars = {
+ ID : null,
+ LATLNG : null,
+ map : null,
+ marker : null,
+ geocoder : null
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PRIVATE FUNCTIONS FOR MANIPULATING DATA ////////////////////////////////////////////////////
+ var setPosition = function(position) {
+ _self.vars.marker.setPosition(position);
+ _self.vars.map.panTo(position);
+
+ $(_self.vars.cssID + ".gllpZoom").val( _self.vars.map.getZoom() );
+ $(_self.vars.cssID + ".gllpLongitude").val( position.lng() );
+ $(_self.vars.cssID + ".gllpLatitude").val( position.lat() );
+
+ $(_self.vars.cssID).trigger("location_changed", $(_self.vars.cssID));
+
+ if (_self.params.queryLocationNameWhenLatLngChanges) {
+ getLocationName(position);
+ }
+ if (_self.params.queryElevationWhenLatLngChanges) {
+ getElevation(position);
+ }
+ };
+
+ // for reverse geocoding
+ var getLocationName = function(position) {
+ var latlng = new google.maps.LatLng(position.lat(), position.lng());
+ _self.vars.geocoder.geocode({'latLng': latlng}, function(results, status) {
+ if (status == google.maps.GeocoderStatus.OK && results[1]) {
+ $(_self.vars.cssID + ".gllpLocationName").val(results[1].formatted_address);
+ } else {
+ $(_self.vars.cssID + ".gllpLocationName").val("");
+ }
+ $(_self.vars.cssID).trigger("location_name_changed", $(_self.vars.cssID));
+ });
+ };
+
+ // for getting the elevation value for a position
+ var getElevation = function(position) {
+ var latlng = new google.maps.LatLng(position.lat(), position.lng());
+
+ var locations = [latlng];
+
+ var positionalRequest = { 'locations': locations };
+
+ _self.vars.elevator.getElevationForLocations(positionalRequest, function(results, status) {
+ if (status == google.maps.ElevationStatus.OK) {
+ if (results[0]) {
+ $(_self.vars.cssID + ".gllpElevation").val( results[0].elevation.toFixed(3));
+ } else {
+ $(_self.vars.cssID + ".gllpElevation").val("");
+ }
+ } else {
+ $(_self.vars.cssID + ".gllpElevation").val("");
+ }
+ $(_self.vars.cssID).trigger("elevation_changed", $(_self.vars.cssID));
+ });
+ };
+
+ // search function
+ var performSearch = function(string, silent) {
+ if (string == "") {
+ if (!silent) {
+ displayError( _self.params.strings.error_empty_field );
+ }
+ return;
+ }
+ _self.vars.geocoder.geocode(
+ {"address": string},
+ function(results, status) {
+ if (status == google.maps.GeocoderStatus.OK) {
+ $(_self.vars.cssID + ".gllpZoom").val(11);
+ _self.vars.map.setZoom( parseInt($(_self.vars.cssID + ".gllpZoom").val()) );
+ setPosition( results[0].geometry.location );
+ } else {
+ if (!silent) {
+ displayError( _self.params.strings.error_no_results );
+ }
+ }
+ }
+ );
+ };
+
+ // error function
+ var displayError = function(message) {
+ alert(message);
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // PUBLIC FUNCTIONS //////////////////////////////////////////////////////////////////////////
+ var publicfunc = {
+
+ // INITIALIZE MAP ON DIV //////////////////////////////////////////////////////////////////
+ init : function(object) {
+
+ if ( !$(object).attr("id") ) {
+ if ( $(object).attr("name") ) {
+ $(object).attr("id", $(object).attr("name") );
+ } else {
+ $(object).attr("id", "_MAP_" + Math.ceil(Math.random() * 10000) );
+ }
+ }
+
+ _self.vars.ID = $(object).attr("id");
+ _self.vars.cssID = "#" + _self.vars.ID + " ";
+
+ _self.params.defLat = $(_self.vars.cssID + ".gllpLatitude").val() ? $(_self.vars.cssID + ".gllpLatitude").val() : _self.params.defLat;
+ _self.params.defLng = $(_self.vars.cssID + ".gllpLongitude").val() ? $(_self.vars.cssID + ".gllpLongitude").val() : _self.params.defLng;
+ _self.params.defZoom = $(_self.vars.cssID + ".gllpZoom").val() ? parseInt($(_self.vars.cssID + ".gllpZoom").val()) : _self.params.defZoom;
+
+ _self.vars.LATLNG = new google.maps.LatLng(_self.params.defLat, _self.params.defLng);
+
+ _self.vars.MAPOPTIONS = _self.params.mapOptions;
+ _self.vars.MAPOPTIONS.zoom = _self.params.defZoom;
+ _self.vars.MAPOPTIONS.center = _self.vars.LATLNG;
+
+ _self.vars.map = new google.maps.Map($(_self.vars.cssID + ".gllpMap").get(0), _self.vars.MAPOPTIONS);
+ _self.vars.geocoder = new google.maps.Geocoder();
+ _self.vars.elevator = new google.maps.ElevationService();
+
+ _self.vars.marker = new google.maps.Marker({
+ position: _self.vars.LATLNG,
+ map: _self.vars.map,
+ title: _self.params.strings.markerText,
+ draggable: true
+ });
+
+ // Set position on doubleclick
+ google.maps.event.addListener(_self.vars.map, 'dblclick', function(event) {
+ setPosition(event.latLng);
+ });
+
+ // Set position on marker move
+ google.maps.event.addListener(_self.vars.marker, 'dragend', function(event) {
+ setPosition(_self.vars.marker.position);
+ });
+
+ // Set zoom feld's value when user changes zoom on the map
+ google.maps.event.addListener(_self.vars.map, 'zoom_changed', function(event) {
+ $(_self.vars.cssID + ".gllpZoom").val( _self.vars.map.getZoom() );
+ $(_self.vars.cssID).trigger("location_changed", $(_self.vars.cssID));
+ });
+
+ // Update location and zoom values based on input field's value
+ $(_self.vars.cssID + ".gllpUpdateButton").bind("click", function() {
+ var lat = $(_self.vars.cssID + ".gllpLatitude").val();
+ var lng = $(_self.vars.cssID + ".gllpLongitude").val();
+ var latlng = new google.maps.LatLng(lat, lng);
+ _self.vars.map.setZoom( parseInt( $(_self.vars.cssID + ".gllpZoom").val() ) );
+ setPosition(latlng);
+ });
+
+ // Search function by search button
+ $(_self.vars.cssID + ".gllpSearchButton").bind("click", function() {
+ performSearch( $(_self.vars.cssID + ".gllpSearchField").val(), false );
+ });
+
+ // Search function by gllp_perform_search listener
+ $(document).bind("gllp_perform_search", function(event, object) {
+ performSearch( $(object).attr('string'), true );
+ });
+
+ // Zoom function triggered by gllp_perform_zoom listener
+ $(document).bind("gllp_update_fields", function(event) {
+ var lat = $(_self.vars.cssID + ".gllpLatitude").val();
+ var lng = $(_self.vars.cssID + ".gllpLongitude").val();
+ var latlng = new google.maps.LatLng(lat, lng);
+ _self.vars.map.setZoom( parseInt( $(_self.vars.cssID + ".gllpZoom").val() ) );
+ setPosition(latlng);
+ });
+
+ /* local modification */
+ window.gMapsLatLonPickerState[_self.vars.ID] =
+ {
+ vars : _self.vars,
+ params : _self.params
+ };
+ } // publicfunc
+
+ }
+
+ return publicfunc;
+});
+
+}(jQuery));
+
+$(document).ready( function() {
+ $(".gllpLatlonPicker").each(function() {
+ $(document).gMapsLatLonPicker().init( $(this) );
+ });
+});
+
+$(document).bind("location_changed", function(event, object) {
+ console.log("changed: " + $(object).attr('id') );
+});
diff --git a/httemplate/elements/mapselect.html b/httemplate/elements/mapselect.html new file mode 100644 index 000000000..7d1447f98 --- /dev/null +++ b/httemplate/elements/mapselect.html @@ -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 index dacb5ba92..000000000 --- a/httemplate/elements/tower_sector.html +++ /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> diff --git a/httemplate/elements/tr-tower_sectors.html b/httemplate/elements/tr-tower_sectors.html index 5351becd8..4e8f3fb47 100644 --- a/httemplate/elements/tr-tower_sectors.html +++ b/httemplate/elements/tr-tower_sectors.html @@ -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()); diff --git a/httemplate/misc/sector_coverage-json.cgi b/httemplate/misc/sector_coverage-json.cgi index 9fd08d7db..37595f5e2 100644 --- a/httemplate/misc/sector_coverage-json.cgi +++ b/httemplate/misc/sector_coverage-json.cgi @@ -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; diff --git a/httemplate/search/tower-map.html b/httemplate/search/tower-map.html index 914457d89..4460db8fe 100755 --- a/httemplate/search/tower-map.html +++ b/httemplate/search/tower-map.html @@ -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 |