/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the Clear BSD license. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the * full text of the license. */ /** * @requires OpenLayers/Control.js * @requires OpenLayers/BaseTypes.js * @requires OpenLayers/Events.js */ /** * Class: OpenLayers.Control.OverviewMap * The OverMap control creates a small overview map, useful to display the * extent of a zoomed map and your main map and provide additional * navigation options to the User. By default the overview map is drawn in * the lower right corner of the main map. Create a new overview map with the * constructor. * * Inerits from: * - */ OpenLayers.Control.OverviewMap = OpenLayers.Class(OpenLayers.Control, { /** * Property: element * {DOMElement} The DOM element that contains the overview map */ element: null, /** * APIProperty: ovmap * {} A reference to the overview map itself. */ ovmap: null, /** * APIProperty: size * {} The overvew map size in pixels. Note that this is * the size of the map itself - the element that contains the map (default * class name olControlOverviewMapElement) may have padding or other style * attributes added via CSS. */ size: new OpenLayers.Size(180, 90), /** * APIProperty: layers * {Array()} Ordered list of layers in the overview map. * If none are sent at construction, the base layer for the main map is used. */ layers: null, /** * APIProperty: minRectSize * {Integer} The minimum width or height (in pixels) of the extent * rectangle on the overview map. When the extent rectangle reaches * this size, it will be replaced depending on the value of the * property. Default is 15 pixels. */ minRectSize: 15, /** * APIProperty: minRectDisplayClass * {String} Replacement style class name for the extent rectangle when * is reached. This string will be suffixed on to the * displayClass. Default is "RectReplacement". * * Example CSS declaration: * (code) * .olControlOverviewMapRectReplacement { * overflow: hidden; * cursor: move; * background-image: url("img/overview_replacement.gif"); * background-repeat: no-repeat; * background-position: center; * } * (end) */ minRectDisplayClass: "RectReplacement", /** * APIProperty: minRatio * {Float} The ratio of the overview map resolution to the main map * resolution at which to zoom farther out on the overview map. */ minRatio: 8, /** * APIProperty: maxRatio * {Float} The ratio of the overview map resolution to the main map * resolution at which to zoom farther in on the overview map. */ maxRatio: 32, /** * APIProperty: mapOptions * {Object} An object containing any non-default properties to be sent to * the overview map's map constructor. These should include any * non-default options that the main map was constructed with. */ mapOptions: null, /** * APIProperty: autoPan * {Boolean} Always pan the overview map, so the extent marker remains in * the center. Default is false. If true, when you drag the extent * marker, the overview map will update itself so the marker returns * to the center. */ autoPan: false, /** * Property: handlers * {Object} */ handlers: null, /** * Property: resolutionFactor * {Object} */ resolutionFactor: 1, /** * APIProperty: maximized * {Boolean} Start as maximized (visible). Defaults to false. */ maximized: false, /** * Constructor: OpenLayers.Control.OverviewMap * Create a new overview map * * Parameters: * object - {Object} Properties of this object will be set on the overview * map object. Note, to set options on the map object contained in this * control, set as one of the options properties. */ initialize: function(options) { this.layers = []; this.handlers = {}; OpenLayers.Control.prototype.initialize.apply(this, [options]); }, /** * APIMethod: destroy * Deconstruct the control */ destroy: function() { if (!this.mapDiv) { // we've already been destroyed return; } if (this.handlers.click) { this.handlers.click.destroy(); } if (this.handlers.drag) { this.handlers.drag.destroy(); } this.mapDiv.removeChild(this.extentRectangle); this.extentRectangle = null; if (this.rectEvents) { this.rectEvents.destroy(); this.rectEvents = null; } if (this.ovmap) { this.ovmap.destroy(); this.ovmap = null; } this.element.removeChild(this.mapDiv); this.mapDiv = null; this.div.removeChild(this.element); this.element = null; if (this.maximizeDiv) { OpenLayers.Event.stopObservingElement(this.maximizeDiv); this.div.removeChild(this.maximizeDiv); this.maximizeDiv = null; } if (this.minimizeDiv) { OpenLayers.Event.stopObservingElement(this.minimizeDiv); this.div.removeChild(this.minimizeDiv); this.minimizeDiv = null; } this.map.events.un({ "moveend": this.update, "changebaselayer": this.baseLayerDraw, scope: this }); OpenLayers.Control.prototype.destroy.apply(this, arguments); }, /** * Method: draw * Render the control in the browser. */ draw: function() { OpenLayers.Control.prototype.draw.apply(this, arguments); if(!(this.layers.length > 0)) { if (this.map.baseLayer) { var layer = this.map.baseLayer.clone(); this.layers = [layer]; } else { this.map.events.register("changebaselayer", this, this.baseLayerDraw); return this.div; } } // create overview map DOM elements this.element = document.createElement('div'); this.element.className = this.displayClass + 'Element'; this.element.style.display = 'none'; this.mapDiv = document.createElement('div'); this.mapDiv.style.width = this.size.w + 'px'; this.mapDiv.style.height = this.size.h + 'px'; this.mapDiv.style.position = 'relative'; this.mapDiv.style.overflow = 'hidden'; this.mapDiv.id = OpenLayers.Util.createUniqueID('overviewMap'); this.extentRectangle = document.createElement('div'); this.extentRectangle.style.position = 'absolute'; this.extentRectangle.style.zIndex = 1000; //HACK this.extentRectangle.className = this.displayClass+'ExtentRectangle'; this.mapDiv.appendChild(this.extentRectangle); this.element.appendChild(this.mapDiv); this.div.appendChild(this.element); // Optionally add min/max buttons if the control will go in the // map viewport. if(!this.outsideViewport) { this.div.className += " " + this.displayClass + 'Container'; var imgLocation = OpenLayers.Util.getImagesLocation(); // maximize button div var img = imgLocation + 'layer-switcher-maximize.png'; this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv( this.displayClass + 'MaximizeButton', null, new OpenLayers.Size(18,18), img, 'absolute'); this.maximizeDiv.style.display = 'none'; this.maximizeDiv.className = this.displayClass + 'MaximizeButton'; 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'; this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv( 'OpenLayers_Control_minimizeDiv', null, new OpenLayers.Size(18,18), img, 'absolute'); this.minimizeDiv.style.display = 'none'; this.minimizeDiv.className = this.displayClass + 'MinimizeButton'; OpenLayers.Event.observe(this.minimizeDiv, 'click', OpenLayers.Function.bindAsEventListener(this.minimizeControl, this) ); this.div.appendChild(this.minimizeDiv); var eventsToStop = ['dblclick','mousedown']; for (var i=0, len=eventsToStop.length; i} The pixel location of the drag. */ rectDrag: function(px) { var deltaX = this.handlers.drag.last.x - px.x; var deltaY = this.handlers.drag.last.y - px.y; if(deltaX != 0 || deltaY != 0) { var rectTop = this.rectPxBounds.top; var rectLeft = this.rectPxBounds.left; var rectHeight = Math.abs(this.rectPxBounds.getHeight()); var rectWidth = this.rectPxBounds.getWidth(); // don't allow dragging off of parent element var newTop = Math.max(0, (rectTop - deltaY)); newTop = Math.min(newTop, this.ovmap.size.h - this.hComp - rectHeight); var newLeft = Math.max(0, (rectLeft - deltaX)); newLeft = Math.min(newLeft, this.ovmap.size.w - this.wComp - rectWidth); this.setRectPxBounds(new OpenLayers.Bounds(newLeft, newTop + rectHeight, newLeft + rectWidth, newTop)); } }, /** * Method: mapDivClick * Handle browser events * * Parameters: * evt - {} evt */ mapDivClick: function(evt) { var pxCenter = this.rectPxBounds.getCenterPixel(); var deltaX = evt.xy.x - pxCenter.x; var deltaY = evt.xy.y - pxCenter.y; var top = this.rectPxBounds.top; var left = this.rectPxBounds.left; var height = Math.abs(this.rectPxBounds.getHeight()); var width = this.rectPxBounds.getWidth(); var newTop = Math.max(0, (top + deltaY)); newTop = Math.min(newTop, this.ovmap.size.h - height); var newLeft = Math.max(0, (left + deltaX)); newLeft = Math.min(newLeft, this.ovmap.size.w - width); this.setRectPxBounds(new OpenLayers.Bounds(newLeft, newTop + height, newLeft + width, newTop)); this.updateMapToRect(); }, /** * Method: maximizeControl * Unhide the control. Called when the control is in the map viewport. * * Parameters: * e - {} */ maximizeControl: function(e) { this.element.style.display = ''; this.showToggle(false); if (e != null) { OpenLayers.Event.stop(e); } }, /** * Method: minimizeControl * Hide all the contents of the control, shrink the size, * add the maximize icon * * Parameters: * e - {} */ minimizeControl: function(e) { this.element.style.display = 'none'; this.showToggle(true); if (e != null) { OpenLayers.Event.stop(e); } }, /** * Method: showToggle * Hide/Show the toggle depending on whether the control is minimized * * Parameters: * minimize - {Boolean} */ showToggle: function(minimize) { this.maximizeDiv.style.display = minimize ? '' : 'none'; this.minimizeDiv.style.display = minimize ? 'none' : ''; }, /** * Method: update * Update the overview map after layers move. */ update: function() { if(this.ovmap == null) { this.createMap(); } if(this.autoPan || !this.isSuitableOverview()) { this.updateOverview(); } // update extent rectangle this.updateRectToMap(); }, /** * Method: isSuitableOverview * Determines if the overview map is suitable given the extent and * resolution of the main map. */ isSuitableOverview: function() { var mapExtent = this.map.getExtent(); var maxExtent = this.map.maxExtent; var testExtent = new OpenLayers.Bounds( Math.max(mapExtent.left, maxExtent.left), Math.max(mapExtent.bottom, maxExtent.bottom), Math.min(mapExtent.right, maxExtent.right), Math.min(mapExtent.top, maxExtent.top)); if (this.ovmap.getProjection() != this.map.getProjection()) { testExtent = testExtent.transform( this.map.getProjectionObject(), this.ovmap.getProjectionObject() ); } var resRatio = this.ovmap.getResolution() / this.map.getResolution(); return ((resRatio > this.minRatio) && (resRatio <= this.maxRatio) && (this.ovmap.getExtent().containsBounds(testExtent))); }, /** * Method updateOverview * Called by if returns true */ updateOverview: function() { var mapRes = this.map.getResolution(); var targetRes = this.ovmap.getResolution(); var resRatio = targetRes / mapRes; if(resRatio > this.maxRatio) { // zoom in overview map targetRes = this.minRatio * mapRes; } else if(resRatio <= this.minRatio) { // zoom out overview map targetRes = this.maxRatio * mapRes; } var center; if (this.ovmap.getProjection() != this.map.getProjection()) { center = this.map.center.clone(); center.transform(this.map.getProjectionObject(), this.ovmap.getProjectionObject() ); } else { center = this.map.center; } this.ovmap.setCenter(center, this.ovmap.getZoomForResolution( targetRes * this.resolutionFactor)); this.updateRectToMap(); }, /** * Method: createMap * Construct the map that this control contains */ createMap: function() { // create the overview map var options = OpenLayers.Util.extend( {controls: [], maxResolution: 'auto', fallThrough: false}, this.mapOptions); this.ovmap = new OpenLayers.Map(this.mapDiv, options); // prevent ovmap from being destroyed when the page unloads, because // the OverviewMap control has to do this (and does it). OpenLayers.Event.stopObserving(window, 'unload', this.ovmap.unloadDestroy); this.ovmap.addLayers(this.layers); this.ovmap.zoomToMaxExtent(); // check extent rectangle border width this.wComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle, 'border-left-width')) + parseInt(OpenLayers.Element.getStyle(this.extentRectangle, 'border-right-width')); this.wComp = (this.wComp) ? this.wComp : 2; this.hComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle, 'border-top-width')) + parseInt(OpenLayers.Element.getStyle(this.extentRectangle, 'border-bottom-width')); this.hComp = (this.hComp) ? this.hComp : 2; this.handlers.drag = new OpenLayers.Handler.Drag( this, {move: this.rectDrag, done: this.updateMapToRect}, {map: this.ovmap} ); this.handlers.click = new OpenLayers.Handler.Click( this, { "click": this.mapDivClick },{ "single": true, "double": false, "stopSingle": true, "stopDouble": true, "pixelTolerance": 1, map: this.ovmap } ); this.handlers.click.activate(); this.rectEvents = new OpenLayers.Events(this, this.extentRectangle, null, true); this.rectEvents.register("mouseover", this, function(e) { if(!this.handlers.drag.active && !this.map.dragging) { this.handlers.drag.activate(); } }); this.rectEvents.register("mouseout", this, function(e) { if(!this.handlers.drag.dragging) { this.handlers.drag.deactivate(); } }); if (this.ovmap.getProjection() != this.map.getProjection()) { var sourceUnits = this.map.getProjectionObject().getUnits() || this.map.units || this.map.baseLayer.units; var targetUnits = this.ovmap.getProjectionObject().getUnits() || this.ovmap.units || this.ovmap.baseLayer.units; this.resolutionFactor = sourceUnits && targetUnits ? OpenLayers.INCHES_PER_UNIT[sourceUnits] / OpenLayers.INCHES_PER_UNIT[targetUnits] : 1; } }, /** * Method: updateRectToMap * Updates the extent rectangle position and size to match the map extent */ updateRectToMap: function() { // If the projections differ we need to reproject var bounds; if (this.ovmap.getProjection() != this.map.getProjection()) { bounds = this.map.getExtent().transform( this.map.getProjectionObject(), this.ovmap.getProjectionObject() ); } else { bounds = this.map.getExtent(); } var pxBounds = this.getRectBoundsFromMapBounds(bounds); if (pxBounds) { this.setRectPxBounds(pxBounds); } }, /** * Method: updateMapToRect * Updates the map extent to match the extent rectangle position and size */ updateMapToRect: function() { var lonLatBounds = this.getMapBoundsFromRectBounds(this.rectPxBounds); if (this.ovmap.getProjection() != this.map.getProjection()) { lonLatBounds = lonLatBounds.transform( this.ovmap.getProjectionObject(), this.map.getProjectionObject() ); } this.map.panTo(lonLatBounds.getCenterLonLat()); }, /** * Method: setRectPxBounds * Set extent rectangle pixel bounds. * * Parameters: * pxBounds - {} */ setRectPxBounds: function(pxBounds) { var top = Math.max(pxBounds.top, 0); var left = Math.max(pxBounds.left, 0); var bottom = Math.min(pxBounds.top + Math.abs(pxBounds.getHeight()), this.ovmap.size.h - this.hComp); var right = Math.min(pxBounds.left + pxBounds.getWidth(), this.ovmap.size.w - this.wComp); var width = Math.max(right - left, 0); var height = Math.max(bottom - top, 0); if(width < this.minRectSize || height < this.minRectSize) { this.extentRectangle.className = this.displayClass + this.minRectDisplayClass; var rLeft = left + (width / 2) - (this.minRectSize / 2); var rTop = top + (height / 2) - (this.minRectSize / 2); this.extentRectangle.style.top = Math.round(rTop) + 'px'; this.extentRectangle.style.left = Math.round(rLeft) + 'px'; this.extentRectangle.style.height = this.minRectSize + 'px'; this.extentRectangle.style.width = this.minRectSize + 'px'; } else { this.extentRectangle.className = this.displayClass + 'ExtentRectangle'; this.extentRectangle.style.top = Math.round(top) + 'px'; this.extentRectangle.style.left = Math.round(left) + 'px'; this.extentRectangle.style.height = Math.round(height) + 'px'; this.extentRectangle.style.width = Math.round(width) + 'px'; } this.rectPxBounds = new OpenLayers.Bounds( Math.round(left), Math.round(bottom), Math.round(right), Math.round(top) ); }, /** * Method: getRectBoundsFromMapBounds * Get the rect bounds from the map bounds. * * Parameters: * lonLatBounds - {} * * Returns: * {}A bounds which is the passed-in map lon/lat extent * translated into pixel bounds for the overview map */ getRectBoundsFromMapBounds: function(lonLatBounds) { var leftBottomLonLat = new OpenLayers.LonLat(lonLatBounds.left, lonLatBounds.bottom); var rightTopLonLat = new OpenLayers.LonLat(lonLatBounds.right, lonLatBounds.top); var leftBottomPx = this.getOverviewPxFromLonLat(leftBottomLonLat); var rightTopPx = this.getOverviewPxFromLonLat(rightTopLonLat); var bounds = null; if (leftBottomPx && rightTopPx) { bounds = new OpenLayers.Bounds(leftBottomPx.x, leftBottomPx.y, rightTopPx.x, rightTopPx.y); } return bounds; }, /** * Method: getMapBoundsFromRectBounds * Get the map bounds from the rect bounds. * * Parameters: * pxBounds - {} * * Returns: * {} Bounds which is the passed-in overview rect bounds * translated into lon/lat bounds for the overview map */ getMapBoundsFromRectBounds: function(pxBounds) { var leftBottomPx = new OpenLayers.Pixel(pxBounds.left, pxBounds.bottom); var rightTopPx = new OpenLayers.Pixel(pxBounds.right, pxBounds.top); var leftBottomLonLat = this.getLonLatFromOverviewPx(leftBottomPx); var rightTopLonLat = this.getLonLatFromOverviewPx(rightTopPx); return new OpenLayers.Bounds(leftBottomLonLat.lon, leftBottomLonLat.lat, rightTopLonLat.lon, rightTopLonLat.lat); }, /** * Method: getLonLatFromOverviewPx * Get a map location from a pixel location * * Parameters: * overviewMapPx - {} * * Returns: * {} Location which is the passed-in overview map * OpenLayers.Pixel, translated into lon/lat by the overview map */ getLonLatFromOverviewPx: function(overviewMapPx) { var size = this.ovmap.size; var res = this.ovmap.getResolution(); var center = this.ovmap.getExtent().getCenterLonLat(); var delta_x = overviewMapPx.x - (size.w / 2); var delta_y = overviewMapPx.y - (size.h / 2); return new OpenLayers.LonLat(center.lon + delta_x * res , center.lat - delta_y * res); }, /** * Method: getOverviewPxFromLonLat * Get a pixel location from a map location * * Parameters: * lonlat - {} * * Returns: * {} Location which is the passed-in OpenLayers.LonLat, * translated into overview map pixels */ getOverviewPxFromLonLat: function(lonlat) { var res = this.ovmap.getResolution(); var extent = this.ovmap.getExtent(); var px = null; if (extent) { px = new OpenLayers.Pixel( Math.round(1/res * (lonlat.lon - extent.left)), Math.round(1/res * (extent.top - lonlat.lat))); } return px; }, CLASS_NAME: 'OpenLayers.Control.OverviewMap' });