Vishful thinking…

Creating a LEAN and FAST custom marker overlay for Google Maps

Posted in Uncategorized by viswaug on December 18, 2008

Before I start, I need to say that the link to the code below was provided to me by Pamela Fox. The sample below illustrates how to create a light weight custom marker GOverlay for Google maps that is REALLY FAST. And it allows for a multitude of customizations as you see fit. It was just too good not to share. Please find the the tweak I made to the code to allow for it to do a little bit of thematic mapping in just minutes. The demo below also allows you to check out the performance of the custom overlay with large number of markers loaded.

CustomGOverlay

Here are the reasons provided by Pamela Fox for the default marker overlay in Google Maps to be slower than the custom marker overlay below.

The default marker class (GMarker) is designed so that it works well across-browsers, has hot spot shape, can be draggable, has a shadow, prints well in any browser, etc. It is not optimized for performance.

Here is the custom marker overlay code sample.

   1: /* A Bar is a simple overlay that outlines a lat/lng bounds on the
   2:  * map. It has a border of the given weight and color and can optionally
   3:  * have a semi-transparent background color.
   4:  * @param latlng {GLatLng} Point to place bar at.
   5:  * @param opts {Object Literal} Passes configuration options - 
   6:  *   weight, color, height, width, text, and offset.
   7:  */
   8: function MarkerLight(latlng, opts) {
   9:   this.latlng = latlng;
  10:  
  11:   if (!opts) opts = {};
  12:  
  13:   this.height_ = opts.height || 32;
  14:   this.width_ = opts.width || 32;
  15:   this.image_ = opts.image;
  16:   this.imageOver_ = opts.imageOver;
  17:   this.clicked_ = 0;
  18: }
  19:  
  20: /* MarkerLight extends GOverlay class from the Google Maps API
  21:  */
  22: MarkerLight.prototype = new GOverlay();
  23:  
  24: /* Creates the DIV representing this MarkerLight.
  25:  * @param map {GMap2} Map that bar overlay is added to.
  26:  */
  27: MarkerLight.prototype.initialize = function(map) {
  28:   var me = this;
  29:  
  30:   // Create the DIV representing our MarkerLight
  31:   var div = document.createElement("div");
  32:   div.style.border = "1px solid white";
  33:   div.style.position = "absolute";
  34:   div.style.paddingLeft = "0px";
  35:   div.style.cursor = 'pointer';
  36:  
  37:   var img = document.createElement("img");
  38:   img.src = me.image_;
  39:   img.style.width = me.width_ + "px";
  40:   img.style.height = me.height_ + "px";
  41:   div.appendChild(img);  
  42:  
  43:   GEvent.addDomListener(div, "click", function(event) {
  44:     me.clicked_ = 1;
  45:     GEvent.trigger(me, "click");
  46:   });
  47:  
  48:   map.getPane(G_MAP_MARKER_PANE).appendChild(div);
  49:  
  50:   this.map_ = map;
  51:   this.div_ = div;
  52: };
  53:  
  54: /* Remove the main DIV from the map pane
  55:  */
  56: MarkerLight.prototype.remove = function() {
  57:   this.div_.parentNode.removeChild(this.div_);
  58: };
  59:  
  60: /* Copy our data to a new MarkerLight
  61:  * @return {MarkerLight} Copy of bar
  62:  */
  63: MarkerLight.prototype.copy = function() {
  64:   var opts = {};
  65:   opts.color = this.color_;
  66:   opts.height = this.height_;
  67:   opts.width = this.width_;
  68:   opts.image = this.image_;
  69:   opts.imageOver = this.image_;
  70:   return new MarkerLight(this.latlng, opts);
  71: };
  72:  
  73: /* Redraw the MarkerLight based on the current projection and zoom level
  74:  * @param force {boolean} Helps decide whether to redraw overlay
  75:  */
  76: MarkerLight.prototype.redraw = function(force) {
  77:  
  78:   // We only need to redraw if the coordinate system has changed
  79:   if (!force) return;
  80:  
  81:   // Calculate the DIV coordinates of two opposite corners 
  82:   // of our bounds to get the size and position of our MarkerLight
  83:   var divPixel = this.map_.fromLatLngToDivPixel(this.latlng);
  84:  
  85:   // Now position our DIV based on the DIV coordinates of our bounds
  86:   this.div_.style.width = this.width_ + "px";
  87:   this.div_.style.left = (divPixel.x) + "px"
  88:   this.div_.style.height = (this.height_) + "px";
  89:   this.div_.style.top = (divPixel.y) - this.height_ + "px";
  90: };
  91:  
  92: MarkerLight.prototype.getZIndex = function(m) {
  93:   return GOverlay.getZIndex(marker.getPoint().lat())-m.clicked*10000;
  94: }
  95:  
  96: MarkerLight.prototype.getPoint = function() {
  97:   return this.latlng;
  98: };
  99:  
 100: MarkerLight.prototype.setStyle = function(style) {
 101:   for (s in style) {
 102:     this.div_.style[s] = style[s];
 103:   }
 104: };
 105:  
 106: MarkerLight.prototype.setImage = function(image) {
 107:   this.div_.style.background = 'url("' + image + '")';
 108: }
 109:  

7 Responses

Subscribe to comments with RSS.

  1. Pamela Fox said, on December 18, 2008 at 4:52 pm

    Nice thematic mapping with them!

  2. Max Phelps said, on December 19, 2008 at 3:36 am

    Vish,
    Thanks for posting this code. I had done something similar based on my deductions from the Redfin Developer Blog post about switching to Google Maps, but my code was not as complete as the above. Google’s custom overlay can also be used to create a “single image” overlay which should be handy for 1) rendering labels that don’t cutoff, 2) dynamically displaying/highlighting complex polygons, 3) perhaps dynamically overlaying WMS layers without having to cache them on your server.

    All you have to do is change the redraw function to
    1. get the current map extent (using the getBounds() function) along with the map height and width,
    2. pass those inputs to a handler (in the case of ASP.NET) that returns the map image,
    3. Set that image as the div’s background image, and
    4. Set the div’s height and width to the height and width of the map image.

  3. viswaug said, on December 19, 2008 at 3:49 am

    Hi Max,

    I think the ground overlay or the tile overlay would be a better option for the “single image overlay” that you are talking about. I have built a tile overlay layer to display WMS map images which works pretty good. I hope to be posting about it pretty soon.

    Thank You,
    Vish

  4. Max Phelps said, on December 20, 2008 at 3:59 am

    Vish,
    I reimplemented my code using the GGroundOverlay and it’s a lot faster. On the coding side, it seems to be an “either/or” scenario. In each case, I’m doing essentially the same thing (just in different places in the code), but the performance of the ground overlay is significantly better. Thanks for the advice.

  5. Tom said, on January 9, 2009 at 9:35 am

    This is a pretty cool concept I think it will work really well for me. Thanks to both you and Pamela for posting it. I just need a little help to get a info window on the click event to work. I have managed to get the info window to work when any marker light is clicked, but it always appears in the same place, presumably the last marker added. When I add one at a time it works as expected. Should I have the listener in the loop ? Any help would be much appreciated.

    Thanks,

    Tom

    for(var i=0;i<numMarkers;i++)
    {var latlng=new GLatLng(southWest.lat()+latSpan*Math.random(),southWest.lng()+lngSpan*Math.random());
    if
    (markerType==”markerlight”)
    {var ML=new MarkerLight(latlng,{height:15,width:15,image:”icons/GIS/Pdot.PNG”,value:(i/numMarkers)*100});
    GEvent.addListener(ML, “click”, function() {
    map.openInfoWindow(ML.latlng, “hi there”);
    });
    map.addOverlay(ML);
    }
    else
    {map.addOverlay(new GMarker(latlng));}}}

  6. Tom said, on January 11, 2009 at 2:07 am

    Answered my own question.

    I overcomplicated things, just treated it like a normal marker and it worked fine.

    Used this as a good example

    http://gmaps-samples.googlecode.com/svn/trunk/photolayer/photolayer.html

  7. Coomer said, on March 28, 2009 at 7:51 am

    Thanks for this! It’s working really well for me. One small change I made was modifying redraw so that my images are centered horizontally by subtracting (width / 2).

    So my changed line in redraw looks like this:

    this.div_.style.left = (divPixel.x - Math.round(this.width_ / 2)) + "px"

    On a side note, does anyone know how to place the markers in the map so that they display but are not clickable? I’m building a heat-map of sorts and when zoomed out, I don’t want the markers to respond to clicks.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: