36

I know that method exists and is documented, but I don't know how to get an MapCanvasProjection object.

Tomas
  • 57,621
  • 49
  • 238
  • 373
Jader Dias
  • 88,211
  • 155
  • 421
  • 625

5 Answers5

37

Look at http://qfox.nl/notes/116

var overlay = new google.maps.OverlayView();
overlay.draw = function() {};
overlay.setMap(map);
var point = overlay.getProjection().fromLatLngToDivPixel(latLng); 

Ugly indeed. Much easier in v2 - another flaw of google api v3!

Velimir Mlaker
  • 10,664
  • 4
  • 46
  • 58
Tomas
  • 57,621
  • 49
  • 238
  • 373
  • 1
    This works perfectly for me initially, but if I pan the map the pixel positions are not updated. But if I then change the zomm level it works as expected again(until the next pan). Am I missing something? – Roger Ertesvag Mar 06 '12 at 17:47
  • 8
    Regarding the previous comment, using fromLatLngToContainerPixel instead of fromLatLngToDivPixel solved my issue with pixel positions not updating after panning the map. – Roger Ertesvag Mar 13 '12 at 07:59
  • 1
    [`google.maps.MapCanvasProjection` object specification, which includes the method `fromLatLngToContainerPixel(latLng:LatLng)`](https://developers.google.com/maps/documentation/javascript/reference?csw=1#MapCanvasProjection) – user664833 Jun 22 '14 at 06:41
34

I think the easiest way is to ignore Google's desire to make our life harder by removing and hiding useful functions instead of adding new ones, and just to write your own methods that do the same thing.

Here's a version of a function somebody posted somewhere else (I can't find it right now), that worked for me:

fromLatLngToPixel: function (position) {
  var scale = Math.pow(2, Map.getZoom());
  var proj = Map.getProjection();
  var bounds = Map.getBounds();

  var nw = proj.fromLatLngToPoint(
    new google.maps.LatLng(
      bounds.getNorthEast().lat(),
      bounds.getSouthWest().lng()
    ));
  var point = proj.fromLatLngToPoint(position);

  return new google.maps.Point(
    Math.floor((point.x - nw.x) * scale),
    Math.floor((point.y - nw.y) * scale));
},

Now you can call it any time and any where you want. I especially needed it for custom context menus, and it does it's job perfectly.

EDIT: I also wrote a reverse function, fromPixelToLatLng that does exactly the opposite. It is simply based on the first one, with some math applied:

fromPixelToLatLng: function (pixel) {
  var scale = Math.pow(2, Map.getZoom());
  var proj = Map.getProjection();
  var bounds = Map.getBounds();

  var nw = proj.fromLatLngToPoint(
    new google.maps.LatLng(
      bounds.getNorthEast().lat(),
      bounds.getSouthWest().lng()
    ));
  var point = new google.maps.Point();

  point.x = pixel.x / scale + nw.x;
  point.y = pixel.y / scale + nw.y;

  return proj.fromPointToLatLng(point);
}
Tiborg
  • 2,304
  • 2
  • 26
  • 33
  • 2
    google's own example: https://developers.google.com/maps/documentation/javascript/examples/map-coordinates – GoTo Aug 13 '14 at 21:23
  • 1
    +1 for clearly expressing what Google is doing in your first paragraph ;-) Besides, he made it a paid service, which we didn't expect at all. – Tomas Jul 04 '19 at 11:08
21

I wasn't satisfied with the answers here. So I did some experiments and found the "simplest" working solution, which is close to Ralph's answer, but hopefully more understandable. (I wish Google makes this feature more accessible!)

First you declare a subclass of OverlayView somewhere like so:

function CanvasProjectionOverlay() {}
CanvasProjectionOverlay.prototype = new google.maps.OverlayView();
CanvasProjectionOverlay.prototype.constructor = CanvasProjectionOverlay;
CanvasProjectionOverlay.prototype.onAdd = function(){};
CanvasProjectionOverlay.prototype.draw = function(){};
CanvasProjectionOverlay.prototype.onRemove = function(){};

Then somewhere else in your code where you instantiate the map, you also instantiate this OverlayView and set its map, like so:

var map = new google.maps.Map(document.getElementById('google-map'), mapOptions);

// Add canvas projection overlay so we can use the LatLng to pixel converter
var canvasProjectionOverlay = new CanvasProjectionOverlay();
canvasProjectionOverlay.setMap(map);

Then, whenever you need to use fromLatLngToContainerPixel, you just do this:

canvasProjectionOverlay.getProjection().fromLatLngToContainerPixel(myLatLng);

Note that because the MapCanvasProjection object will only be available once draw() is called, which is sometime before the map's idle, I suggest creating a boolean "mapInitialized" flag, set it to true on the first map idle callback. And then do what you need to do only after that.

pixelfreak
  • 17,714
  • 12
  • 90
  • 109
13
var map;
// Create your map
MyOverlay.prototype = new google.maps.OverlayView();
MyOverlay.prototype.onAdd = function() { }
MyOverlay.prototype.onRemove = function() { }
MyOverlay.prototype.draw = function() { }
function MyOverlay(map) { this.setMap(map); }
var overlay = new MyOverlay(map);
var projection = overlay.getProjection();
stepanian
  • 11,373
  • 8
  • 43
  • 63
  • I tried this code, and the variable projection appears to be "undefined". Any idea why? Thanks! – Mathieu Dhondt Nov 13 '10 at 21:28
  • 6
    Note: (and this stumped me for a good half-hour) - make sure to create the overlay when you create the map, and NOT when you need the coordinates (via fromLatLngToDivPixel). If you do, you get Uncaught TypeError: Cannot call method 'fromLatLngToDivPixel' of undefined. It looks like Google does some of this stuff asynchronously. – AlexeyMK Aug 11 '11 at 22:37
  • @AlexeyMK This got me too! until i found your comment. – shapeshifter Dec 05 '12 at 04:49
9

To get a MapCanvasProjection you could derive a class from OverlayView and call the getProjection() method which returns a MapCanvasProjection type

onAdd(), draw() and onRemove() must be implemented to derive from OverlayView.

function MyOverlay(options) {
    this.setValues(options);

    var div = this.div_= document.createElement('div');

    div.className = "overlay";
};

// MyOverlay is derived from google.maps.OverlayView
MyOverlay.prototype = new google.maps.OverlayView;

MyOverlay.prototype.onAdd = function() {

    var pane = this.getPanes().overlayLayer;
    pane.appendChild(this.div_);

}

MyOverlay.prototype.onRemove = function() {
    this.div_.parentNode.removeChild(this.div_);
}

MyOverlay.prototype.draw = function() {
    var projection = this.getProjection();
    var position = projection.fromLatLngToDivPixel(this.getMap().getCenter());

    var div = this.div_;
    div.style.left = position.x + 'px';
    div.style.top = position.y + 'px';
    div.style.display = 'block';
};

then when you're creating your map

var OverLayMap = new MyOverlay( { map: map } );

For V2 you should be able to call fromLatLngToDivPixel from your GMap2 instance

var centerPoint = map.fromLatLngToDivPixel(map.getCenter());
Michael G
  • 6,695
  • 2
  • 41
  • 59