/**
 * SNAMP.OpenLayers
 *
 * Module for extentions to the OpenLayers mapping library for use in SNAMP.
 * Note that I'm trying to keep it flat and not replicate OL's reasonable but
 * extensive namespace hierarchy.  Trying to keep it simple.
 */

// Let's create a namespace for our custom code
SNAMP.OpenLayers = function() {
  return {};
}();

SNAMP.OpenLayers.STUDYSITE_EXTENTS = {
  'all':           new OpenLayers.Bounds(-13433954, 4495064, -13307045, 4747427),
  'northern-site': new OpenLayers.Bounds(-13433954, 4728894, -13413979, 4747427),
  'southern-site': new OpenLayers.Bounds(-13319732, 4495064, -13307045, 4507060)
};

if (typeof Proj4js == 'undefined') {
  var msg = "SNAMP.OpenLayers requires the Proj4js library to " + 
            "handle vector reprojections over Google base layers.";
  if (typeof console != 'undefined') console.error(msg);
  throw msg;
};

// Set the Proj4js libPath if Proj4js has been loaded
if (typeof Proj4js != 'undefined') {
  Proj4js.libPath = '/static/js/proj4js/lib/';
}

SNAMP.OpenLayers.PROJECTIONS = {
  GOOG: new OpenLayers.Projection("EPSG:900913"),
  GEOG: new OpenLayers.Projection("EPSG:4326")
};
 
/**
 * Return Bounds object for all the features in a GML layer.
 *
 * TODO: this should probably be a lower namespace, just can't figure out
 * where to put it.
 */
SNAMP.OpenLayers.getVectorFeatureBounds = function(lyr) {
 var bounds = new OpenLayers.Bounds();
 for (var i = lyr.features.length - 1; i >= 0; i--) {
   bounds.extend(lyr.features[i].geometry.getBounds());
 };
 return bounds;
};

/**
 * Add a few methods to the Map class.
 *
 */
OpenLayers.Util.extend(OpenLayers.Map.prototype, {
  closePopups: function() {
    for (var i = this.popups.length - 1; i >= 0; i--){
      this.popups[i].hide();
    };
  }
});

// Because I got sick of stupid IDs with periods in them
OpenLayers.Util.createUniqueID = function(prefix) {
    if (prefix == null) {
        prefix = "id_";
    }
    OpenLayers.Util.lastSeqID += 1; 
    var unique_id = prefix + OpenLayers.Util.lastSeqID;
    unique_id = unique_id.replace(/[\.\s]+/g, '');
    return unique_id;
};


/** 
 * Class: KMLLegend
 * Reads from KML styles and attempts to make a DOM legend. The legend is a
 * UL, with one LI per KML style, id'd by the style id.
 *
 */
SNAMP.OpenLayers.KMLLegend = function(data, options) {
  function parseStyles(xmldata) {
    var format = new OpenLayers.Format.KML({extracStyles: true});
    var nodes = format.getElementsByTagNameNS(xmldata, "*", 'Style');
    var styles = {};
    for (var i = nodes.length - 1; i >= 0; i--){
      var style = format.parseStyle(nodes[i]);
      if(style) {
        styles[style.id] = style;
      }
    };
    
    return styles;
  } // end parseStyles
  
  function buildLegend(styles) {
    var legend = jQuery("<ul></ul>");
    legend.addClass('kmllegend');
    // this might need to be explicitly bound to the current context.  
    // Actually, i'm not really sure why it works without binding...
    $.each(styles, function() {
      var legendItem = jQuery('<li></li>');
      var legendSymbol = jQuery('<span class="legend_symbol"></span>');
      var legendLabel = jQuery('<span class="legend_label"></span>');
      legendItem.attr('id', this.id);
      legendLabel.html(this.id.replace('_', ' '));
      if (applyStyles) {
        // attempt to hack our way around opacity and border.  Won't work in 
        // older browsers or, most likey, IE
        if (this.fillOpacity) {
          var innerSymbol = jQuery('<div></div>');
          innerSymbol.css('background-color', this.fillColor);
          innerSymbol.css('opacity', this.fillOpacity);
          innerSymbol.css('width', '100%');
          innerSymbol.css('height', '100%');
          legendSymbol.append(innerSymbol);
        } else {
          legendSymbol.css('background-color', this.fillColor);
        };
        
        // borders
        legendSymbol.css('border-color', this.strokeColor);
        legendSymbol.css('border-width', this.strokeWidth+'px');
        legendSymbol.css('border-style', 'solid');
        
        legendItem.css('list-style', 'none');
        
        // non-KML styles
        legendSymbol.css('display', 'block');
        legendSymbol.css('width', '10px');
        legendSymbol.css('height', '10px');
        legendSymbol.css('float', 'left');
        legendItem.css('clear', 'left');
        legendSymbol.css('margin-right', '0.5em');
      };
      legendItem.append(legendSymbol);
      legendItem.append(legendLabel);
      legend.append(legendItem);
    });
    
    return legend;
  } // end buildLegend

  // "Constructor"
  
  var options = (typeof options == 'undefined') ? {} : options;
  var applyStyles = true;
  if (typeof options.applyStyles != 'undefined' && !options.applyStyles) {
    var applyStyles = false;
  };
  
  // parse the kml if it's still in a string
  if(typeof data == "string") {
      // data = OpenLayers.Format.XML.prototype.read(data);
      data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
  }
  else if(typeof data == 'undefined') {
    if (typeof console != 'undefined') {
      console.error("KML is missing! KMLLegend requires some KML, either " + 
                    "in a string or already parsed.");
    };
    throw "KML is missing! KMLLegend requires some KML, either in a string " +
          "or already parsed.";
  }
  
  var styles = parseStyles(data);
  var legend = buildLegend(styles);
  
  return {
    styles: styles,
    legend: legend,
    buildLegend: buildLegend
  }
};


/** 
 * Class: KMLLayerSwitcher
 * Just like the OpenLayers LayerSwitcher control, except this takes a hash of
 * KMLLegend objects and keeps the LayerSwitcher updated with them every time
 * it redraws.
 *
 */
SNAMP.OpenLayers.KMLLayerSwitcher = OpenLayers.Class(OpenLayers.Control.LayerSwitcher, {
  
  kmlLegends: {},   // Hash of KMLLegend objects, keyed by layer name
  includeCSS: true, // Do we want to render the default inline CSS?

  /**
   * Constructor: SNAMP.KMLLayerSwitcher
   * 
   * Parameters:
   * kmlLegends - {Object} a hash of KMLLegend objects keyed by layer name
   * options - {Object}
   *   - includeCSS - {Boolean} Do we want to render the default inline CSS?
   *   - (all the normal LayerSwitcher options)
   */  
  initialize: function(kmlLegends, options) {
    this.kmlLegends = kmlLegends;
    if (typeof options != 'undefined' && 
        typeof options.includeCSS != 'undefined') {
      this.includeCSS = options.includeCSS ? true : false;
    };
    OpenLayers.Control.LayerSwitcher.prototype.initialize.apply(
      this, [options]);
  },
  
  /**
   * Method: SNAMP.KMLLayerSwitcher.jMinimizeControl
   * Minimizes the LayerSwitcher using JQuery.
   * 
   * Parameters:
   * options - {Object}
   */
  jMinimizeControl: function() {
    jQuery(this.div).slideUp('fast');
  },
  
  /**
   * Method: SNAMP.KMLLayerSwitcher.jMinimizeControl
   * Maximizes the LayerSwitcher using JQuery.
   * 
   * Parameters:
   * options - {Object}
   */
  jMaximizeControl: function() {
    jQuery(this.div).slideDown('fast');
  },
  
  /**
   * Method: SNAMP.KMLLayerSwitcher.redraw
   * Re-insert the KML legends after the LayerSwticher has redrawn.
   * 
   * Parameters:
   * options - {Object}
   */
  redraw: function() {
    var ls = OpenLayers.Control.LayerSwitcher.prototype.redraw.apply(this, arguments);
    
    // re-insert the KMLLegend DOM elements
    jQuery.each(this.kmlLegends, function(key, val) {
      jQuery(ls).find('#input_'+key+' + span').after(val.legend);
    });
  },
  
  /** 
   * Method: loadContents
   * Set up the labels and divs for the control. This override of the
   * corresponding LayerSwitcher function is identical except that it allows
   * you to ignore all the defaul CSS by setting the includeCSS option to
   * false in the constructor.
   */
  loadContents: function() {

      //configure main div
      if (this.includeCSS) {
        this.div.style.position = "absolute";
        this.div.style.top = "25px";
        this.div.style.right = "0px";
        this.div.style.left = "";
        this.div.style.fontFamily = "sans-serif";
        this.div.style.fontWeight = "bold";
        this.div.style.marginTop = "3px";
        this.div.style.marginLeft = "3px";
        this.div.style.marginBottom = "3px";
        this.div.style.fontSize = "smaller";   
        this.div.style.color = "white";   
        this.div.style.backgroundColor = "transparent";
      };
  
      OpenLayers.Event.observe(this.div, "mouseup", 
          OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
      OpenLayers.Event.observe(this.div, "click",
                    this.ignoreEvent);
      OpenLayers.Event.observe(this.div, "mousedown",
          OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
      OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);


      // layers list div        
      this.layersDiv = document.createElement("div");
      this.layersDiv.id = "layersDiv";
      if (this.includeCSS) {
        this.layersDiv.style.paddingTop = "5px";
        this.layersDiv.style.paddingLeft = "10px";
        this.layersDiv.style.paddingBottom = "5px";
        this.layersDiv.style.paddingRight = "75px";
        this.layersDiv.style.backgroundColor = this.activeColor;
        
        // had to set width/height to get transparency in IE to work.
        // thanks -- http://jszen.blogspot.com/2005/04/ie6-opacity-filter-caveat.html
        //
        this.layersDiv.style.width = "100%";
        this.layersDiv.style.height = "100%";
      };


      this.baseLbl = document.createElement("div");
      this.baseLbl.innerHTML = OpenLayers.i18n("baseLayer");
      this.baseLbl.id = 'baseLbl';
      this.baseLbl.className = 'label';
      
      if (this.includeCSS) {
        this.baseLbl.style.marginTop = "3px";
        this.baseLbl.style.marginLeft = "3px";
        this.baseLbl.style.marginBottom = "3px";
      };
      
      this.baseLayersDiv = document.createElement("div");
      this.baseLayersDiv.id = 'baseLayers';
      this.baseLayersDiv.className = 'layers';
      if (this.includeCSS) {
        this.baseLayersDiv.style.paddingLeft = "10px";
      };
      /*OpenLayers.Event.observe(this.baseLayersDiv, "click", 
          OpenLayers.Function.bindAsEventListener(this.onLayerClick, this));
      */
                   

      this.dataLbl = document.createElement("div");
      this.dataLbl.innerHTML = OpenLayers.i18n("overlays");
      this.dataLbl.id = 'dataLbl';
      this.dataLbl.className = 'label';
      if (this.includeCSS) {
        this.dataLbl.style.marginTop = "3px";
        this.dataLbl.style.marginLeft = "3px";
        this.dataLbl.style.marginBottom = "3px";
      };
      
      this.dataLayersDiv = document.createElement("div");
      this.dataLayersDiv.id = 'overlays';
      this.dataLayersDiv.className = 'layers';
      if (this.includeCSS) {
        this.dataLayersDiv.style.paddingLeft = "10px";
      };

      if (this.ascending) {
          this.layersDiv.appendChild(this.baseLbl);
          this.layersDiv.appendChild(this.baseLayersDiv);
          this.layersDiv.appendChild(this.dataLbl);
          this.layersDiv.appendChild(this.dataLayersDiv);
      } else {
          this.layersDiv.appendChild(this.dataLbl);
          this.layersDiv.appendChild(this.dataLayersDiv);
          this.layersDiv.appendChild(this.baseLbl);
          this.layersDiv.appendChild(this.baseLayersDiv);
      }    

      this.div.appendChild(this.layersDiv);

      if (this.includeCSS) {
        OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
                                        bgColor: "transparent",
                                        color: this.activeColor,
                                        blend: false});

        OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);
      };

      var imgLocation = OpenLayers.Util.getImagesLocation();
      var sz = new OpenLayers.Size(18,18);        

      // maximize button div
      var img = imgLocation + 'layer-switcher-maximize.png';
      this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                  "OpenLayers_Control_MaximizeDiv", 
                                  null, 
                                  sz, 
                                  img, 
                                  "absolute");
      if (this.includeCSS) {
        this.maximizeDiv.style.top = "5px";
        this.maximizeDiv.style.right = "0px";
        this.maximizeDiv.style.left = "";
      };
      this.maximizeDiv.style.display = "none";
      OpenLayers.Event.observe(this.maximizeDiv, "click", 
          OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
      );
      
      this.div.appendChild(this.maximizeDiv);

      // minimize button div
      var img = imgLocation + 'layer-switcher-minimize.png';
      var sz = new OpenLayers.Size(18,18);        
      this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
                                  "OpenLayers_Control_MinimizeDiv", 
                                  null, 
                                  sz, 
                                  img, 
                                  "absolute");
      if (this.includeCSS) {
        this.minimizeDiv.style.top = "5px";
        this.minimizeDiv.style.right = "0px";
        this.minimizeDiv.style.left = "";
      };
      this.minimizeDiv.style.display = "none";
      OpenLayers.Event.observe(this.minimizeDiv, "click", 
          OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
      );

      this.div.appendChild(this.minimizeDiv);
  },
  
  CLASS_NAME: 'SNAMP.OpenLayers.KMLLayerSwitcher'
});


/** 
 * Class: SNAMP.PanZoomBar
 * Once again, I am overriding an OL control JUST to craft some sane, stylable
 * markup. Yay.
 *
 */
SNAMP.OpenLayers.PanZoomBar = OpenLayers.Class(OpenLayers.Control.PanZoomBar, {
  _addZoomBar:function(centered) {
      var imgLocation = OpenLayers.Util.getImagesLocation();
      
      var id = "OpenLayers_Control_PanZoomBar_Slider" + this.map.id;
      id = id.replace(/[\.\s]+/g, '');
      var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom();
      var slider = OpenLayers.Util.createAlphaImageDiv(id,
                     centered.add(-1, zoomsToEnd * this.zoomStopHeight), 
                     new OpenLayers.Size(20,9), 
                     imgLocation+"slider.png",
                     "absolute");
      slider.className = "OpenLayers_Control_PanZoomBar_Slider";
      this.slider = slider;
      
      this.sliderEvents = new OpenLayers.Events(this, slider, null, true);
      this.sliderEvents.on({
          "mousedown": this.zoomBarDown,
          "mousemove": this.zoomBarDrag,
          "mouseup": this.zoomBarUp,
          "dblclick": this.doubleClick,
          "click": this.doubleClick
      });
      
      var sz = new OpenLayers.Size();
      sz.h = this.zoomStopHeight * this.map.getNumZoomLevels();
      sz.w = this.zoomStopWidth;
      var div = null;
      
      if (OpenLayers.Util.alphaHack()) {
          var id = "OpenLayers_Control_PanZoomBar" + this.map.id;
          id = id.replace(/[\.\s]+/g, '');
          div = OpenLayers.Util.createAlphaImageDiv(id, centered,
                                    new OpenLayers.Size(sz.w, 
                                            this.zoomStopHeight),
                                    imgLocation + "zoombar.png", 
                                    "absolute", null, "crop");
          div.style.height = sz.h + "px";
      } else {
          var id = "OpenLayers_Control_PanZoomBar" + this.map.id;
          id = id.replace(/[\.\s]+/g, '');
          div = OpenLayers.Util.createDiv(
                      id,
                      centered,
                      sz,
                      imgLocation+"zoombar.png");
      }
      div.className = "OpenLayers_Control_PanZoomBar_ZoomBar";
      
      this.zoombarDiv = div;
      
      this.divEvents = new OpenLayers.Events(this, div, null, true);
      this.divEvents.on({
          "mousedown": this.divClick,
          "mousemove": this.passEventToSlider,
          "dblclick": this.doubleClick,
          "click": this.doubleClick
      });
      
      this.div.appendChild(div);

      this.startTop = parseInt(div.style.top);
      this.div.appendChild(slider);

      this.map.events.register("zoomend", this, this.moveZoomBar);

      centered = centered.add(0, 
          this.zoomStopHeight * this.map.getNumZoomLevels());
      return centered; 
  },
  CLASS_NAME: 'SNAMP.OpenLayers.PanZoomBar'
});


// /**
//  * SNAMP.Label
//  * Label for a feature.  Renders a label div at the feature's center.
//  
//  * A label must have a feature, but a feature doesn't need a label.
//  *
//  * A Vector Layer can have Labels.
//  *
//  * Labels should be rendered and destroyed along with the features they label
//  * 
//  */
// SNAMP.OpenLayers.FeatureLabel = OpenLayers.Class({
//   feature: null,
//   div: null,
//   icon: null,
//   position: 'center',
//   lonlat: null,
//   
//   initialize: function(feature, position, content, icon) {
//     this.feature = feature;
//     this.feature.label = this;
//     this.lonlat = feature.geometry.bounds.getCenterLonLat().clone();
//     this.position = (position) ? position : 'center';
//     
//     if (typeof content == 'undefined') {
//       var content = '';
//       if (typeof this.feature.data.name != 'undefined') {
//         content = this.feature.data.name;
//       } else {
//         content = this.feature.id;
//       };
//     };
//     this.content = content;
//     
//     this.div = OpenLayers.Util.createDiv();
//     this.div.innerHTML = this.content;
//     // this.feature.layer.div.appendChild(this.div);
//   },
//   
//   draw: function(px, sz) {
//     OpenLayers.Util.modifyDOMElement(this.div, null, px, sz);
//     return this.div;
//   },
//   
//   destroy: function() {
//     this.div = null;
//     this.icon = null;
//     this.position = null;
//     this.lonlat = null;
//     this.feature = null;
//     if (this.icon) { this.icon.destroy(); }
//   },
//   
//   display: function(display) {
//       this.div.style.display = (display) ? "" : "none"; 
//   },
// });
// 
// /**
//  * SNAMP.OpenLayers.GML
//  * Just like the original, except with labels. Right now it only makes one
//  * label per feature, and positions it around the center of the feature's
//  * bounding box. In the future, maybe we should support more sophisticated
//  * labelings.
//  * 
//  * Mostly based on OpenLayers.Layer.Marker
//  *
//  * Ok, I just don't think this will work. If you look at
//  * OpenLayer.Layer.Vector.moveTo, you'll see that it actually seems to
//  * reposition the layer's div on every move, which reaks havoc with the labels
//  * positioning, which are all calculated relative to the layer's div extents.
//  */
// SNAMP.OpenLayers.GML = OpenLayers.Class(OpenLayers.Layer.GML, {
//   labels: [],
//   
//   // override initialize to support label options
//   
//   // this is inideal b/c it won't make labels after dynamically adding features, if that's something you want to do.  I tried overriding Vector.addFeatures, 
//   drawFeature: function(feature, style) {
//     console.log("DEBUG: Running drawFeature...");
//     OpenLayers.Layer.GML.prototype.drawFeature.apply(this, arguments);
//     
//     // Don't make a label if we've already done so
//     var label;
//     for (var i = this.labels.length - 1; i >= 0; i--){
//       if (feature === this.labels[i].feature ) {
//         label = this.labels[i];
//         break;
//       };
//     };
//     
//     if (!label) {
//       label = new SNAMP.OpenLayers.FeatureLabel(feature);
//       this.labels.push(label
//     }
//     
//     this.drawLabel(label);
//   },
//   
//   moveTo: function(bounds, zoomChanged, minor) {
//     OpenLayers.Layer.GML.prototype.moveTo.apply(this, arguments);
//     if (zoomChanged || !this.drawn) {
//       for (var i = this.labels.length - 1; i >= 0; i--){
//         this.drawLabel(this.labels[i]);
//       };
//     };
//   },
//   
//   drawLabel: function(label) {
//     var px = this.map.getLayerPxFromLonLat(label.lonlat);
//     var sz = new OpenLayers.Size(30, 10);
//     if (px == null) {
//         label.display(false);
//     } else {
//       console.log("DEBUG: Drawing label ", label.content);
//         label.draw(px, sz);
//         if (!label.drawn) {
//             this.div.appendChild(label.div);
//             label.drawn = true;
//         }
//     }
//   },
//   
//   removeFeatures: function(features, options) {
//     // destroy the corresponding labels
//     for (var i = this.features.length - 1; i >= 0; i--){
//       for (var j = this.labels.length - 1; j >= 0; j--){
//         if (this.labels[j].feature === this.features[i]) {
//           this.labels[j].destroy();
//           this.labels = OpenLayers.Util.removeItem(this.labels, this.labels[j]);
//         }
//       };
//     };
//     OpenLayers.Layer.GML.prototype.removeFeatures.apply(this, arguments);
//   }
// });

// Custom SNAMPMap. Extends OpenLayers.Map with a few of our default layers +
// controls
SNAMP.OpenLayers.Map = OpenLayers.Class(OpenLayers.Map, {
  messageDiv: null,
  
  initialize: function(div, options) {
    if (typeof options == 'undefined') var options = {};
    OpenLayers.Util.extend(options, {
      // theme: null,
      controls: [],
      projection: new OpenLayers.Projection("EPSG:900913"),
      displayProjection: new OpenLayers.Projection("EPSG:4326"),
      units: "m",
      maxResolution: 156543.0339,
      maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
                                       20037508, 20037508.34)
    });
    
    OpenLayers.Map.prototype.initialize.apply(this, [div, options]);
    
    // Create controls
    var customControls = [
      new OpenLayers.Control.DragPan(), 
      new SNAMP.OpenLayers.PanZoomBar(),
      new SNAMP.OpenLayers.KMLLayerSwitcher(
        {}, 
        { includeCSS: false, div: jQuery('#layer-switcher')[0] }
	  )
	];
    for (var i=0; i<customControls.length; i++) {
      var control = customControls[i];
      this.addControl(control);
      control.activate();
    }
    
    // bind the legend control behaviors
    jQuery('#legend-control').bind('click', function(e) {
      jQuery('#layer-switcher').slideToggle('fast');
      SNAMP.toggleText(jQuery('#legend-control .control'), 'Show', 'Hide');
      SNAMP.toggleClasses(jQuery('#legend-control .control'), 'show', 'hide');
      return false;
    });
    
    // Init message div
    if (typeof(options.messageDiv) != 'undefined') {
      this.messageDiv = messageDiv;
    } else if (jQuery('#map-message').length > 0) {
      this.messageDiv = jQuery('#map-message')[0];
    }
      
    
    // Default Base layers
    var gTerrainLyr = new OpenLayers.Layer.Google(
      "Google Terrain", {type: G_PHYSICAL_MAP, 'sphericalMercator': true});
    var gMapLyr = new OpenLayers.Layer.Google(
      "Google Map", {type: G_NORMAL_MAP, 'sphericalMercator': true});
    var gSatelliteLyr = new OpenLayers.Layer.Google(
      "Google Satellite", {type: G_SATELLITE_MAP, 'sphericalMercator': true});
    this.addLayer(gTerrainLyr);
    this.addLayer(gMapLyr);
    this.addLayer(gSatelliteLyr);
    
    // this.zoomToExtent(SNAMP.OpenLayers.STUDYSITE_EXTENTS.all);
  },
  
  setMessage: function(msg) {
    if (this.messageDiv) {
      jQuery(this.messageDiv).html(msg);
      // jQuery(this.messageDiv).slideUp('normal');
      // jQuery(this.messageDiv).show('slide', { direction: 'up' });
      jQuery(this.messageDiv).show();
    } else {
      alert(msg);
    }
  },
  
  clearMessage: function() {
    if (this.messageDiv) {
      jQuery(this.messageDiv).html('');
      // jQuery(this.messageDiv).slideDown('normal');
      // jQuery(this.messageDiv).hide('slide', { direction: 'down' });
      jQuery(this.messageDiv).hide();
    }
  }
});

SNAMP.OpenLayers.MiniMap = OpenLayers.Class(OpenLayers.Map, {
  initialize: function(div, options) {
    if (typeof options == 'undefined') var options = {};
    OpenLayers.Util.extend(options, {
      // theme: null,
      controls: [],
      projection: new OpenLayers.Projection("EPSG:900913"),
      displayProjection: new OpenLayers.Projection("EPSG:4326"),
      units: "m",
      maxResolution: 156543.0339,
      maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
                                       20037508, 20037508.34)
    });
    
    OpenLayers.Map.prototype.initialize.apply(this, [div, options]);
    
    // Create controls
    var customControls = [
      new OpenLayers.Control.DragPan(), 
      new OpenLayers.Control.PanZoom()
    ];
    for (var i=0; i<customControls.length; i++) {
      var control = customControls[i];
      this.addControl(control);
      control.activate();
    } 
    
    // Default Base layers
    var gTerrainLyr = new OpenLayers.Layer.Google(
      "Google Terrain", {type: G_PHYSICAL_MAP, 'sphericalMercator': true});
    this.addLayer(gTerrainLyr);
  }
});

