Monday, June 7, 2010

How to add context menu to google maps, using api V3

Here's the way how to add context menu to google map, using api V3 and jQuery.



step 1:
while initializing maps, add listener for event "rightclick":

 google.maps.event.addListener(map, "rightclick",function(event){showContextMenu(event.latLng);});

;


step 2:
Body of contextmenu is created in showContextMenu()
      function showContextMenu(caurrentLatLng  ) {
var projection;
var contextmenuDir;
projection = map.getProjection() ;
$('.contextmenu').remove();
contextmenuDir = document.createElement("div");
contextmenuDir.className = 'contextmenu';
contextmenuDir.innerHTML = '<a id="menu1"><div class="context">menu item 1<\/div><\/a>'
+ '<a id="menu2"><div class="context">menu item 2<\/div><\/a>';

$(map.getDiv()).append(contextmenuDir);

setMenuXY(caurrentLatLng);

contextmenuDir.style.visibility = "visible";
}


step 3:
When menu is created we must calculate where to display it, so LatLng is converted into appropriate X,Y.

      function getCanvasXY(caurrentLatLng){
var scale = Math.pow(2, map.getZoom());
var nw = new google.maps.LatLng(
map.getBounds().getNorthEast().lat(),
map.getBounds().getSouthWest().lng()
);
var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
var worldCoordinate = map.getProjection().fromLatLngToPoint(caurrentLatLng);
var caurrentLatLngOffset = new google.maps.Point(
Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
);
return caurrentLatLngOffset;
}


step 4:
Just to avoid displaying menu outside the map, X and Y coordinates are redefined if nessesary

  function setMenuXY(caurrentLatLng){
var mapWidth = $('#map_canvas').width();
var mapHeight = $('#map_canvas').height();
var menuWidth = $('.contextmenu').width();
var menuHeight = $('.contextmenu').height();
var clickedPosition = getCanvasXY(caurrentLatLng);
var x = clickedPosition.x ;
var y = clickedPosition.y ;

if((mapWidth - x ) < menuWidth)//if to close to the map border, decrease x position
x = x - menuWidth;
if((mapHeight - y ) < menuHeight)//if to close to the map border, decrease y position
y = y - menuHeight;

$('.contextmenu').css('left',x );
$('.contextmenu').css('top',y );
};


step 5:
To display context menu in right way we must apply this style:


.contextmenu{
visibility:hidden;
background:#ffffff;
border:1px solid #8888FF;
z-index: 10;
position: relative;
width: 140px;
}
.contextmenu div{
padding-left: 5px
}


And now complete source:

  
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript">
var map;
function initialize() {
var latlng = new google.maps.LatLng(49.814357,19.025956);
var myOptions = {
zoom: 12,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
google.maps.event.addListener(map, "rightclick",function(event){showContextMenu(event.latLng);});

}
function getCanvasXY(caurrentLatLng){
var scale = Math.pow(2, map.getZoom());
var nw = new google.maps.LatLng(
map.getBounds().getNorthEast().lat(),
map.getBounds().getSouthWest().lng()
);
var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
var worldCoordinate = map.getProjection().fromLatLngToPoint(caurrentLatLng);
var caurrentLatLngOffset = new google.maps.Point(
Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
);
return caurrentLatLngOffset;
}
function setMenuXY(caurrentLatLng){
var mapWidth = $('#map_canvas').width();
var mapHeight = $('#map_canvas').height();
var menuWidth = $('.contextmenu').width();
var menuHeight = $('.contextmenu').height();
var clickedPosition = getCanvasXY(caurrentLatLng);
var x = clickedPosition.x ;
var y = clickedPosition.y ;

if((mapWidth - x ) < menuWidth)
x = x - menuWidth;
if((mapHeight - y ) < menuHeight)
y = y - menuHeight;

$('.contextmenu').css('left',x );
$('.contextmenu').css('top',y );
};
function showContextMenu(caurrentLatLng ) {
var projection;
var contextmenuDir;
projection = map.getProjection() ;
$('.contextmenu').remove();
contextmenuDir = document.createElement("div");
contextmenuDir.className = 'contextmenu';
contextmenuDir.innerHTML = "<a id='menu1'><div class=context>menu item 1<\/div><\/a><a id='menu2'><div class=context>menu item 2<\/div><\/a>";
$(map.getDiv()).append(contextmenuDir);

setMenuXY(caurrentLatLng);

contextmenuDir.style.visibility = "visible";
}
$(document).ready(function(){
initialize();

});
</script>

<style type="text/css">
#map_canvas{
width: 400px;
height: 300px;
}
.contextmenu{
visibility:hidden;
background:#ffffff;
border:1px solid #8888FF;
z-index: 10;
position: relative;
width: 140px;
}
.contextmenu div{
padding-left: 5px
}
</style>
<div class="formDiv" id="map_canvas"></div>

11 comments:

  1. Thanks a lot. This is very helpful. However I was also hoping to get the code where one could select either of the menus and then fire events based on the menu item chosen.

    ReplyDelete
  2. Here is a jquery solution : http://gmap3.net/examples/context-menu.html

    ReplyDelete
  3. Just thought I'd pop in to say thanks for the help. I was able to use your code as an example for my context menu. However, I anticipated that there would be issues with the international dateline. I thought I would add a bit to the code to fix this issue.

    Issue: When the IDL is present on the map, the context menu will not display to the east of the line.

    My solution: I modified the getCanvasXY() function. Not only do we grab the northwest(nw) point of the map, we also need the northeast point of the map. Then we need to find out if the northwest x - northeast x is greater than a certain amount. This has a lot of dependence upon the zoom level of course. So I used the scale for comparison. Note: this doesn't fix the problem with zoom levels less than 3. The main issue with zooms lower than three is that your x/y conversion will have multiple map points at those zoom levels. So using if you are using this method, set your minimum zoom to 3.

    Here's my revised version of the getCanvasXY() function:
    function getCanvasXY(currentLatLng) {
    var scale = Math.pow(2, map.getZoom());
    var nw = new google.maps.LatLng(map.getBounds().getNorthEast().lat(), map.getBounds().getSouthWest().lng());
    var ne = new google.maps.LatLng(map.getBounds().getNorthEast().lat(), map.getBounds().getNorthEast().lng());
    var worldCoordinateNE = map.getProjection().fromLatLngToPoint(ne);
    var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
    var worldCoordinate = map.getProjection().fromLatLngToPoint(currentLatLng);
    if(((worldCoordinateNW.x - worldCoordinateNE.x) > scale) && (worldCoordinate.x < worldCoordinateNW.x)) {
    worldCoordinate.x = worldCoordinate.x + 256;
    if(scale==1) { }
    }
    var currentLatLngOffset = new google.maps.Point(Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale), Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale));
    return currentLatLngOffset;
    }

    ReplyDelete
  4. Thanks for this tip, very useful. How can I use this for markers as well? I need 2 context menus:
    1- Right click map, where there's no marker
    2- Right click a marker.

    different options for each.

    thanks in advance,
    awni

    ReplyDelete
    Replies
    1. map.addListener('rightclick', function(e){...});

      marker.addListerner('rightclick', function(e){...});

      Delete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Thanks for the help, I found this very useful!

    There is no way for the menu to disappear if it was an accidental click without clicking an option though. The menu will just stay there or reposition if right clicked again in a different location.

    It would be nice to say have it display:none; if visibility="visible"; but I am not sure how that can be implemented.

    ReplyDelete
    Replies
    1. Add this listener in the initialize() method:

      google.maps.event.addListener(map, 'click', function() {
      $('.contextmenu').remove();
      });

      Delete
  7. This comment has been removed by the author.

    ReplyDelete