Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML Canvas As Overlayview In Google Maps That Is Fixed Relative to the Map Canvas

I'm trying to create an HTML5 canvas as an OverlayView the size of a map, position it to top:0; left:0;, draw some stuff on it, and add it to the map. Whenever the map zooms or pans I want to remove the old canvas from the map and create a new canvas draw on it position it to 0,0 and add it to the map. However the map never reposition to top:0; left:0. Can someone help?

    function CustomLayer(map){
this.latlngs = new Array();
this.map_ = map;

this.addMarker = function(position){
this.latlngs.push(position);
}

this.drawCanvas = function(){
this.setMap(this.map_);
//google.maps.event.addListener(this.map_, 'bounds_changed',this.reDraw());
}

}

function defineOverlay() {

CustomLayer.prototype = new google.maps.OverlayView();

CustomLayer.prototype.onAdd = function() {
    console.log("onAdd()");
    if(this.canvas){    
    var panes = this.getPanes();
    panes.overlayLayer.appendChild(this.canvas);
    }
}


CustomLayer.prototype.remove = function() {
    console.log("onRemove()");
    if(this.canvas)
    this.canvas.parentNode.removeChild(this.canvas);
}


CustomLayer.prototype.draw = function() {
    console.log("draw()");
        this.remove();
            this.canvas = document.createElement("canvas");
            this.canvas.setAttribute('width', '800px');
            this.canvas.setAttribute('height', '480px');
            this.canvas.setAttribute('top', '30px');
            this.canvas.setAttribute('left', '30px');
            this.canvas.setAttribute('position', 'absolute');
            this.canvas.setAttribute('border', '1px solid red');
            this.canvas.style.border = '1px solid red';

            //using this way for some reason scale up the images and mess up the positions of the markers
            /*this.canvas.style.position = 'absolute';
            this.canvas.style.top = '0px';
            this.canvas.style.left = '0px';
            this.canvas.style.width = '800px'; 
            this.canvas.style.height = '480px';
            this.canvas.style.border = '1px solid red';*/

            //get the projection from this overlay
            overlayProjection = this.getProjection();
            //var mapproj = this.map_.getProjection();

                if(this.canvas.getContext) {
                    var context = this.canvas.getContext('2d');
                    context.clearRect(0,0,800,480);

                    for(i=0; i<this.latlngs.length; i++){

                        p = overlayProjection.fromLatLngToDivPixel(this.latlngs[i]);
                        //p = mapproj.fromLatLngToPoint(this.latlngs[i]);
                        img = new Image();
                        img.src = "standardtick.png";
                            console.log(Math.floor(p.x)+","+Math.floor(p.y));
                    context.drawImage(img,p.x,p.y);
                    }
                }
    this.onAdd();           
    console.log("canvas width:"+this.canvas.width+" canvas height: "+this.canvas.height);
    console.log("canvas top:"+this.canvas.getAttribute("top")+" left: "+this.canvas.getAttribute("left"));  
}
}
like image 957
dpham Avatar asked May 11 '11 07:05

dpham


1 Answers

In this example - I think it is important to draw attention to the difference between projection.fromLatLngToDivPixel and projection.fromLatLngToContainerPixel. In this context, DivPixel is used to keep the position of the canvas centered over the map view - while ContainerPixel is used to find the positions of the shapes you are drawing to the canvas.

What follows is a complete working example I worked out while playing around with this problem myself.

Required CSS Properties for the Overlay:

  .GMAPS_OVERLAY
  {
    border-width: 0px;
    border: none;
    position:absolute;
    padding:0px 0px 0px 0px;
    margin:0px 0px 0px 0px;
  }

Initialize the Map and Create a Test based on Google Markers

  var mapsize    = { width: 500, height: 500 };
  var mapElement = document.getElementById("MAP");

  mapElement.style.height = mapsize.width + "px";
  mapElement.style.width  = mapsize.width + "px";

  var map = new google.maps.Map(document.getElementById("MAP"), {
    mapTypeId: google.maps.MapTypeId.TERRAIN,
    center:    new google.maps.LatLng(0, 0),
    zoom:      2
  });

  // Render G-Markers to Test Proper Canvas-Grid Alignment
  for (var lng = -180; lng < 180; lng += 10)
  {
    var marker = new google.maps.Marker({
      position: new google.maps.LatLng(0, lng),
      map: map
    });
  }

Define the Custom Overlay

  var CanvasOverlay = function(map) {
    this.canvas           = document.createElement("CANVAS");
    this.canvas.className = "GMAPS_OVERLAY";
    this.canvas.height    = mapsize.height;
    this.canvas.width     = mapsize.width;
    this.ctx              = null;
    this.map              = map;

    this.setMap(map);
  };
  CanvasOverlay.prototype = new google.maps.OverlayView();

  CanvasOverlay.prototype.onAdd = function() {
    this.getPanes().overlayLayer.appendChild(this.canvas);
    this.ctx = this.canvas.getContext("2d");
    this.draw();
  };

  CanvasOverlay.prototype.drawLine = function(p1, p2) {
    this.ctx.beginPath();
    this.ctx.moveTo( p1.x, p1.y );
    this.ctx.lineTo( p2.x, p2.y );
    this.ctx.closePath();
    this.ctx.stroke();
  };

  CanvasOverlay.prototype.draw = function() {
    var projection = this.getProjection();

    // Shift the Canvas
    var centerPoint = projection.fromLatLngToDivPixel(this.map.getCenter());
    this.canvas.style.left = (centerPoint.x - mapsize.width  / 2) + "px";
    this.canvas.style.top  = (centerPoint.y - mapsize.height / 2) + "px";

    // Clear the Canvas
    this.ctx.clearRect(0, 0, mapsize.width, mapsize.height);

    // Draw Grid with Canvas
    this.ctx.strokeStyle = "#000000";
    for (var lng = -180; lng < 180; lng += 10)
    {
      this.drawLine(
        projection.fromLatLngToContainerPixel(new google.maps.LatLng(-90, lng)),
        projection.fromLatLngToContainerPixel(new google.maps.LatLng( 90, lng))
      );
    }
  };

Initializing the Canvas

I find that I like to add an additional call to draw on the "dragend" event - but test it out to see what you think on your needs.

  var customMapCanvas = new CanvasOverlay(map);
  google.maps.event.addListener(map, "drawend", function() {
    customMapCanvas.draw();
  };

In cases where Canvas Drawing is Slowing Down Map

On the applications I work with, I find that the Map Framework calls the 'draw' method far too often on canvas's which are drawing something that takes a second or so to complete. In this case I define the 'draw' prototype function to be simply an empty function while naming my real draw function as 'canvasDraw' - then add event listeners for "zoomend" and "dragend". What you get here is a canvas that updates only after a user changes the zoom level or at the end of a map drag action.

  CanvasOverlay.prototype.draw = function() { };      

  ... 

  google.maps.event.addListener(map, "dragend", function() {
    customMapCanvas.canvasDraw();
  });

  google.maps.event.addListener(map, "zoom_changed", function() {
    customMapCanvas.canvasDraw();
  });

Live Demo: Complete Example - All Inline Source

like image 94
John Avatar answered Nov 13 '22 14:11

John