1 /* Copyright (c) 2006 MetaCarta, Inc., published under the BSD license.
 
   2  * See http://svn.openlayers.org/trunk/openlayers/license.txt for the full
 
   3  * text of the license. */
 
   4 // @require: OpenLayers/Util.js
 
  11 OpenLayers.Map = Class.create();
 
  12 OpenLayers.Map.prototype = {
 
  13     // Hash: base z-indexes for different classes of thing 
 
  14     Z_INDEX_BASE: { Layer: 100, Popup: 200, Control: 1000 },
 
  16     // Array: supported application event types
 
  18         "addlayer", "removelayer", "movestart", "move", "moveend",
 
  19         "zoomend", "layerchanged", "popupopen", "popupclose",
 
  20         "addmarker", "removemarker", "clearmarkers", "mouseover",
 
  21         "mouseout", "mousemove", "dragstart", "drag", "dragend" ],
 
  23     // int: zoom levels, used to draw zoom dragging control and limit zooming
 
  27     maxExtent: new OpenLayers.Bounds(-180, -90, 180, 90),
 
  30     projection: "EPSG:4326",
 
  32     /** @type OpenLayers.Size */
 
  36     maxResolution: 1.40625, // degrees per pixel 
 
  37                             // Default is whole world in 256 pixels, from GMaps
 
  39     // DOMElement: the div that our map lives in
 
  42     // HTMLDivElement: the map's view port             
 
  45     // HTMLDivElement: the map's layer container
 
  46     layerContainerDiv: null,
 
  48     // Array(OpenLayers.Layer): ordered list of layers in the map
 
  51     // Array(OpenLayers.Control)
 
  54     // Array(OpenLayers.Popup)
 
  69     /** @type OpenLayers.Layer */
 
  73     * @param {DOMElement} div
 
  75     initialize: function (div, options) {
 
  76         Object.extend(this, options);
 
  78         this.div = div = $(div);
 
  80         // the viewPortDiv is the outermost div we modify
 
  81         var id = div.id + "_OpenLayers_ViewPort";
 
  82         this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
 
  85         this.viewPortDiv.style.width = "100%";
 
  86         this.viewPortDiv.style.height = "100%";
 
  87         this.div.appendChild(this.viewPortDiv);
 
  89         // the layerContainerDiv is the one that holds all the layers
 
  90         id = div.id + "_OpenLayers_Container";
 
  91         this.layerContainerDiv = OpenLayers.Util.createDiv(id);
 
  92         this.viewPortDiv.appendChild(this.layerContainerDiv);
 
  94         this.events = new OpenLayers.Events(this, div, this.EVENT_TYPES);
 
  97         // make the entire maxExtent fix in zoom level 0 by default
 
  98         if (this.maxResolution == null || this.maxResolution == "auto") {
 
  99             this.maxResolution = Math.max(
 
 100                 this.maxExtent.getWidth()  / this.size.w,
 
 101                 this.maxExtent.getHeight() / this.size.h );
 
 103         // update the internal size register whenever the div is resized
 
 104         this.events.register("resize", this, this.updateSize);
 
 108         if (!this.controls) {
 
 110             this.addControl(new OpenLayers.Control.MouseDefaults());
 
 111             this.addControl(new OpenLayers.Control.PanZoom());
 
 114         this.popups = new Array();
 
 116         // always call map.destroy()
 
 117         Event.observe(window, 'unload', 
 
 118             this.destroy.bindAsEventListener(this));
 
 125         if (this.layers != null) {
 
 126             for(var i=0; i< this.layers.length; i++) {
 
 127                 this.layers[i].destroy();
 
 131         if (this.controls != null) {
 
 132             for(var i=0; i< this.controls.length; i++) {
 
 133                 this.controls[i].destroy();
 
 135             this.controls = null;
 
 140     * @param {OpenLayers.Layer} layer
 
 142     addLayer: function (layer) {
 
 144         layer.div.style.overflow = "";
 
 145         layer.div.style.zIndex = this.Z_INDEX_BASE['Layer'] + this.layers.length;
 
 147         if (layer.viewPortLayer) {
 
 148             this.viewPortDiv.appendChild(layer.div);
 
 150             this.layerContainerDiv.appendChild(layer.div);
 
 152         this.layers.push(layer);
 
 154         // hack hack hack - until we add a more robust layer switcher,
 
 155         //   which is able to determine which layers are base layers and 
 
 156         //   which are not (and put baselayers in a radiobutton group and 
 
 157         //   other layers in checkboxes) this seems to be the most straight-
 
 158         //   forward way of dealing with this. 
 
 160         if (layer.isBaseLayer()) {
 
 161             this.baseLayer = layer;
 
 163         this.events.triggerEvent("addlayer");
 
 166     /** Removes a layer from the map by removing its visual element (the 
 
 167      *   layer.div property), then removing it from the map's internal list 
 
 168      *   of layers, setting the layer's map property to null. 
 
 170      *   a "removelayer" event is triggered.
 
 172      *   very worthy of mention is that simply removing a layer from a map
 
 173      *   will not cause the removal of any popups which may have been created
 
 174      *   by the layer. this is due to the fact that it was decided at some
 
 175      *   point that popups would not belong to layers. thus there is no way 
 
 176      *   for us to know here to which layer the popup belongs.
 
 178      *     A simple solution to this is simply to call destroy() on the layer.
 
 179      *     the default OpenLayers.Layer class's destroy() function
 
 180      *     automatically takes care to remove itself from whatever map it has
 
 183      *     The correct solution is for the layer itself to register an 
 
 184      *     event-handler on "removelayer" and when it is called, if it 
 
 185      *     recognizes itself as the layer being removed, then it cycles through
 
 186      *     its own personal list of popups, removing them from the map.
 
 188      * @param {OpenLayers.Layer} layer
 
 190     removeLayer: function(layer) {
 
 191         this.layerContainerDiv.removeChild(layer.div);
 
 192         this.layers.remove(layer);
 
 194         this.events.triggerEvent("removelayer");
 
 198     * @param {Array(OpenLayers.Layer)} layers
 
 200     addLayers: function (layers) {
 
 201         for (var i = 0; i <  layers.length; i++) {
 
 202             this.addLayer(layers[i]);
 
 207     * @param {OpenLayers.Control} control
 
 208     * @param {OpenLayers.Pixel} px
 
 210     addControl: function (control, px) {
 
 212         this.controls.push(control);
 
 213         var div = control.draw(px);
 
 215             div.style.zIndex = this.Z_INDEX_BASE['Control'] +
 
 216                                 this.controls.length;
 
 217             this.viewPortDiv.appendChild( div );
 
 222     * @param {OpenLayers.Popup} popup
 
 224     addPopup: function(popup) {
 
 226         this.popups.push(popup);
 
 227         var popupDiv = popup.draw();
 
 229             popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
 
 231             this.layerContainerDiv.appendChild(popupDiv);
 
 236     * @param {OpenLayers.Popup} popup
 
 238     removePopup: function(popup) {
 
 239         this.popups.remove(popup);
 
 241             this.layerContainerDiv.removeChild(popup.div);
 
 249     getResolution: function () {
 
 250         // return degrees per pixel
 
 251         return this.maxResolution / Math.pow(2, this.zoom);
 
 257     getZoom: function () {
 
 262     * @returns {OpenLayers.Size}
 
 264     getSize: function () {
 
 271     updateSize: function() {
 
 272         this.size = new OpenLayers.Size(
 
 273                     this.div.clientWidth, this.div.clientHeight);
 
 274         this.events.div.offsets = null;
 
 275         // Workaround for the fact that hidden elements return 0 for size.
 
 276         if (this.size.w == 0 && this.size.h == 0) {
 
 277             var dim = Element.getDimensions(this.div);
 
 278             this.size.w = dim.width;
 
 279             this.size.h = dim.height;
 
 281         if (this.size.w == 0 && this.size.h == 0) {
 
 282             this.size.w = parseInt(this.div.style.width);
 
 283             this.size.h = parseInt(this.div.style.height);
 
 288     * @return {OpenLayers.LonLat}
 
 290     getCenter: function () {
 
 295     * @return {OpenLayers.Bounds}
 
 297     getExtent: function () {
 
 299             var res = this.getResolution();
 
 300             var size = this.getSize();
 
 301             var w_deg = size.w * res;
 
 302             var h_deg = size.h * res;
 
 303             return new OpenLayers.Bounds(
 
 304                 this.center.lon - w_deg / 2, 
 
 305                 this.center.lat - h_deg / 2,
 
 306                 this.center.lon + w_deg / 2,
 
 307                 this.center.lat + h_deg / 2);
 
 314     * @return {OpenLayers.Bounds}
 
 316     getFullExtent: function () {
 
 317         return this.maxExtent;
 
 320     getZoomLevels: function() {
 
 321         return this.maxZoomLevel;
 
 325     * @param {OpenLayers.Bounds} bounds
 
 329     getZoomForExtent: function (bounds) {
 
 330         var size = this.getSize();
 
 331         var width = bounds.getWidth();
 
 332         var height = bounds.getHeight();
 
 333         var deg_per_pixel = (width > height ? width / size.w : height / size.h);
 
 334         var zoom = Math.log(this.maxResolution / deg_per_pixel) / Math.log(2);
 
 335         return Math.floor(Math.min(Math.max(zoom, 0), this.getZoomLevels())); 
 
 339      * @param {OpenLayers.Pixel} layerPx
 
 341      * @returns px translated into view port pixel coordinates
 
 342      * @type OpenLayers.Pixel
 
 345     getViewPortPxFromLayerPx:function(layerPx) {
 
 346         var viewPortPx = layerPx.copyOf();
 
 348         viewPortPx.x += parseInt(this.layerContainerDiv.style.left);
 
 349         viewPortPx.y += parseInt(this.layerContainerDiv.style.top);
 
 355      * @param {OpenLayers.Pixel} viewPortPx
 
 357      * @returns px translated into view port pixel coordinates
 
 358      * @type OpenLayers.Pixel
 
 361     getLayerPxFromViewPortPx:function(viewPortPx) {
 
 362         var layerPx = viewPortPx.copyOf();
 
 364         layerPx.x -= parseInt(this.layerContainerDiv.style.left);
 
 365         layerPx.y -= parseInt(this.layerContainerDiv.style.top);
 
 372     * @param {OpenLayers.Pixel} px
 
 374     * @return {OpenLayers.LonLat} 
 
 376     getLonLatFromLayerPx: function (px) {
 
 377        //adjust for displacement of layerContainerDiv
 
 378        px = this.getViewPortPxFromLayerPx(px);
 
 379        return this.getLonLatFromViewPortPx(px);         
 
 383     * @param {OpenLayers.Pixel} viewPortPx
 
 385     * @returns An OpenLayers.LonLat which is the passed-in view port
 
 386     *          OpenLayers.Pixel, translated into lon/lat given the 
 
 387     *          current extent and resolution
 
 388     * @type OpenLayers.LonLat
 
 391     getLonLatFromViewPortPx: function (viewPortPx) {
 
 392         var center = this.getCenter();        //map center lon/lat
 
 393         var res  = this.getResolution();
 
 394         var size = this.getSize();
 
 396         var delta_x = viewPortPx.x - (size.w / 2);
 
 397         var delta_y = viewPortPx.y - (size.h / 2);
 
 399         return new OpenLayers.LonLat(center.lon + delta_x * res ,
 
 400                                      center.lat - delta_y * res); 
 
 403     // getLonLatFromPixel is a convenience function for the API
 
 405     * @param {OpenLayers.Pixel} pixel
 
 407     * @returns An OpenLayers.LonLat corresponding to the given
 
 408     *          OpenLayers.Pixel, translated into lon/lat using the 
 
 409     *          current extent and resolution
 
 410     * @type OpenLayers.LonLat
 
 412     getLonLatFromPixel: function (px) {
 
 413         return this.getLonLatFromViewPortPx(px);
 
 417     * @param {OpenLayers.LonLat} lonlat
 
 419     * @returns An OpenLayers.Pixel which is the passed-in OpenLayers.LonLat, 
 
 420     *          translated into layer pixels given the current extent 
 
 422     * @type OpenLayers.Pixel
 
 424     getLayerPxFromLonLat: function (lonlat) {
 
 425        //adjust for displacement of layerContainerDiv
 
 426        var px = this.getViewPortPxFromLonLat(lonlat);
 
 427        return this.getLayerPxFromViewPortPx(px);         
 
 431     * @param {OpenLayers.LonLat} lonlat
 
 433     * @returns An OpenLayers.Pixel which is the passed-in OpenLayers.LonLat, 
 
 434     *          translated into view port pixels given the current extent 
 
 436     * @type OpenLayers.Pixel
 
 439     getViewPortPxFromLonLat: function (lonlat) {
 
 440         var resolution = this.getResolution();
 
 441         var extent = this.getExtent();
 
 442         return new OpenLayers.Pixel(
 
 443                        Math.round(1/resolution * (lonlat.lon - extent.left)),
 
 444                        Math.round(1/resolution * (extent.top - lonlat.lat))
 
 448     // getLonLatFromPixel is a convenience function for the API
 
 450     * @param {OpenLayers.LonLat} lonlat
 
 452     * @returns An OpenLayers.Pixel corresponding to the OpenLayers.LonLat
 
 453     *          translated into view port pixels using the current extent 
 
 455     * @type OpenLayers.Pixel
 
 457     getPixelFromLonLat: function (lonlat) {
 
 458         return this.getViewPortPxFromLonLat(lonlat);
 
 462     * @param {OpenLayers.LonLat} lonlat
 
 465     setCenter: function (lonlat, zoom, minor) {
 
 466         if (this.center) { // otherwise there's nothing to move yet
 
 467             this.moveLayerContainer(lonlat);
 
 469         this.center = lonlat.copyOf();
 
 470         var zoomChanged = null;
 
 471         if (zoom != null && zoom != this.zoom 
 
 472             && zoom >= 0 && zoom <= this.getZoomLevels()) {
 
 473             zoomChanged = (this.zoom == null ? 0 : this.zoom);
 
 477         if (!minor) this.events.triggerEvent("movestart");
 
 478         this.moveToNewExtent(zoomChanged, minor);
 
 479         if (!minor) this.events.triggerEvent("moveend");
 
 483      * ZOOM TO BOUNDS FUNCTION
 
 486     moveToNewExtent: function (zoomChanged, minor) {
 
 487         if (zoomChanged != null) { // reset the layerContainerDiv's location
 
 488             this.layerContainerDiv.style.left = "0px";
 
 489             this.layerContainerDiv.style.top  = "0px";
 
 492             for (var i = 0; i < this.popups.length; i++) {
 
 493                 this.popups[i].updatePosition();
 
 497         var bounds = this.getExtent();
 
 498         for (var i = 0; i < this.layers.length; i++) {
 
 499             this.layers[i].moveTo(bounds, (zoomChanged != null), minor);
 
 501         this.events.triggerEvent("move");
 
 502         if (zoomChanged != null)
 
 503             this.events.triggerEvent("zoomend", 
 
 504                 {oldZoom: zoomChanged, newZoom: this.zoom});
 
 509      * Increase zoom level by one.
 
 513         if (this.zoom != null && this.zoom <= this.getZoomLevels()) {
 
 514             this.zoomTo( this.zoom += 1 );
 
 523     zoomTo: function(zoom) {
 
 524        if (zoom >= 0 && zoom <= this.getZoomLevels()) {
 
 525             var oldZoom = this.zoom;
 
 527             this.moveToNewExtent(oldZoom);
 
 533      * Decrease zoom level by one.
 
 536     zoomOut: function() {
 
 537         if (this.zoom != null && this.zoom > 0) {
 
 538             this.zoomTo( this.zoom - 1 );
 
 544      * Zoom to the full extent and recenter.
 
 546     zoomToFullExtent: function() {
 
 547         var fullExtent = this.getFullExtent();
 
 549           new OpenLayers.LonLat((fullExtent.left+fullExtent.right)/2,
 
 550                                 (fullExtent.bottom+fullExtent.top)/2),
 
 551           this.getZoomForExtent(fullExtent)
 
 556     * @param {OpenLayers.LonLat} lonlat
 
 559     moveLayerContainer: function (lonlat) {
 
 560         var container = this.layerContainerDiv;
 
 561         var resolution = this.getResolution();
 
 563         var deltaX = Math.round((this.center.lon - lonlat.lon) / resolution);
 
 564         var deltaY = Math.round((this.center.lat - lonlat.lat) / resolution);
 
 566         var offsetLeft = parseInt(container.style.left);
 
 567         var offsetTop  = parseInt(container.style.top);
 
 569         container.style.left = (offsetLeft + deltaX) + "px";
 
 570         container.style.top  = (offsetTop  - deltaY) + "px";
 
 573     CLASS_NAME: "OpenLayers.Map"