/** * @name Key Drag Zoom for V3 * @version 1.0 * @author: Nianwei Liu [nianwei at gmail dot com] e Gary Little [gary at luxcentral dot com] * @fileoverview This library adds a drag zoom capability to a Google map. * When drag zoom is enabled, holding down a user-defined hot key (shift | ctrl | alt) * while dragging a box around an area of interest will zoom the map * to that area when the hot key is released. * Only one line of code is needed: google.maps.Map.enableKeyDragZoom(); *

* Note that if the map's container has a border around it, the border widths must be specified * in pixel units (or as thin, medium, or thick). This is required because of an MSIE limitation. *

NL: 2009-05-28: initial port to core API V3. */ /*! * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function () { /*jslint browser:true */ /*global google */ /** * Converts 'thin', 'medium', and 'thick' to pixel widths * in an MSIE environment. Not called for other browsers * because getComputedStyle() returns pixel widths automatically. * @param {String} widthValue */ var toPixels = function (widthValue) { var px; switch (widthValue) { case 'thin': px = "2px"; break; case 'medium': px = "4px"; break; case 'thick': px = "6px"; break; default: px = widthValue; } return px; }; /** * Get the widths of the borders of an HTML element. * * @param {Object} h HTML element * @return {Object} widths object (top, bottom left, right) */ var getBorderWidths = function (h) { var computedStyle; var bw = {}; if (document.defaultView && document.defaultView.getComputedStyle) { computedStyle = h.ownerDocument.defaultView.getComputedStyle(h, ""); if (computedStyle) { // The computed styles are always in pixel units (good!) bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0; bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0; bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0; return bw; } } else if (document.documentElement.currentStyle) { // MSIE if (h.currentStyle) { // The current styles may not be in pixel units so try to convert (bad!) bw.top = parseInt(toPixels(h.currentStyle.borderTopWidth), 10) || 0; bw.bottom = parseInt(toPixels(h.currentStyle.borderBottomWidth), 10) || 0; bw.left = parseInt(toPixels(h.currentStyle.borderLeftWidth), 10) || 0; bw.right = parseInt(toPixels(h.currentStyle.borderRightWidth), 10) || 0; return bw; } } // Shouldn't get this far for any modern browser bw.top = parseInt(h.style["border-top-width"], 10) || 0; bw.bottom = parseInt(h.style["border-bottom-width"], 10) || 0; bw.left = parseInt(h.style["border-left-width"], 10) || 0; bw.right = parseInt(h.style["border-right-width"], 10) || 0; return bw; }; /** * Get the position of the mouse relative to the document. * @param {Object} e Mouse event * @return {Object} left & top position */ var getMousePosition = function (e) { var posX = 0, posY = 0; e = e || window.event; if (typeof e.pageX !== "undefined") { posX = e.pageX; posY = e.pageY; } else if (typeof e.clientX !== "undefined") { posX = e.clientX + (typeof document.documentElement.scrollLeft !== "undefined" ? document.documentElement.scrollLeft : document.body.scrollLeft); posY = e.clientY + (typeof document.documentElement.scrollTop !== "undefined" ? document.documentElement.scrollTop : document.body.scrollTop); } return { left: posX, top: posY }; }; /** * Get the position of an HTML element relative to the document. * @param {Object} h HTML element * @return {Object} left & top position */ var getElementPosition = function (h) { var posX = h.offsetLeft; var posY = h.offsetTop; var parent = h.offsetParent; // Add offsets for all ancestors in the hierarchy while (parent !== null) { // Adjust for scrolling elements which may affect the map position. // // See http://www.howtocreate.co.uk/tutorials/javascript/browserspecific // // "...make sure that every element [on a Web page] with an overflow // of anything other than visible also has a position style set to // something other than the default static..." if (parent !== document.body && parent !== document.documentElement) { posX -= parent.scrollLeft; posY -= parent.scrollTop; } posX += parent.offsetLeft; posY += parent.offsetTop; parent = parent.offsetParent; } return { left: posX, top: posY }; }; /** * Set the properties of an object to those from another object. * @param {Object} obj target object * @param {Object} vals source object */ var setVals = function (obj, vals) { if (obj && vals) { for (var x in vals) { if (vals.hasOwnProperty(x)) { obj[x] = vals[x]; } } } return obj; }; /** * Set the opacity. If op is not passed in, this function just performs an MSIE fix. * @param {Node} div * @param {Number} op (0-1) */ var setOpacity = function (div, op) { if (typeof op !== 'undefined') { div.style.opacity = op; } if (typeof div.style.opacity !== 'undefined') { div.style.filter = "alpha(opacity=" + (div.style.opacity * 100) + ")"; } }; /** * @name KeyDragZoomOptions * @class This class represents the optional parameter passed into google.maps.Map.enableDragBoxZoom. * @property {String} [key] the hot key to hold down to activate a drag zoom, shift | ctrl | alt. * The default is shift. * @property {Object} [boxStyle] the css style of the zoom box. * The default is {border: 'thin solid #FF0000'}. * Border widths must be specified in pixel units (or as thin, medium, or thick). * @property {Object} [paneStyle] the css style of the pane which overlays the map when a drag zoom is activated. * The default is {backgroundColor: 'white', opacity: 0.0, cursor: 'crosshair'}. */ /** * @name DragZoom * @class This class represents a drag zoom object for a map. The object is activated by holding down the hot key. * This object is created when google.maps.Map.enableKeyDragZoom is called; it cannot be created directly. * Use google.maps.Map.getDragZoomObject to gain access to this object in order to attach event listeners. * @param {google.maps.Map} map * @param {KeyDragZoomOptions} opt_zoomOpts */ function DragZoom(map, opt_zoomOpts) { var ov = new google.maps.OverlayView(); var me = this; ov.onAdd = function () { me.init_(map, opt_zoomOpts); }; ov.draw = function () { }; ov.onRemove = function () { }; ov.setMap(map); this.prjov_ = ov; } /** * Init the tool. * @param {google.maps.Map} map * @param {KeyDragZoomOptions} opt_zoomOpts */ DragZoom.prototype.init_ = function (map, opt_zoomOpts) { this.map_ = map; opt_zoomOpts = opt_zoomOpts || {}; this.key_ = opt_zoomOpts.key || 'shift'; this.key_ = this.key_.toLowerCase(); this.borderWidths_ = getBorderWidths(this.map_.getDiv());//Container()); this.paneDiv_ = document.createElement("div"); this.paneDiv_.onselectstart = function () { return false; }; // default style setVals(this.paneDiv_.style, { backgroundColor: 'white', opacity: 0.0, cursor: 'crosshair' }); // allow overwrite setVals(this.paneDiv_.style, opt_zoomOpts.paneStyle); // stuff that cannot be overwritten setVals(this.paneDiv_.style, { position: 'absolute', overflow: 'hidden', zIndex: 10001, display: 'none' }); if (this.key_ === 'shift') { // Workaround for Firefox Shift-Click problem this.paneDiv_.style.MozUserSelect = "none"; } setOpacity(this.paneDiv_); // An IE fix: if the background is transparent, it cannot capture mousedown events if (this.paneDiv_.style.backgroundColor === 'transparent') { this.paneDiv_.style.backgroundColor = 'white'; setOpacity(this.paneDiv_, 0); } this.map_.getDiv().appendChild(this.paneDiv_);//Container() this.boxDiv_ = document.createElement('div'); setVals(this.boxDiv_.style, { border: 'thin solid #FF0000' }); setVals(this.boxDiv_.style, opt_zoomOpts.boxStyle); setVals(this.boxDiv_.style, { position: 'absolute', display: 'none' }); setOpacity(this.boxDiv_); this.map_.getDiv().appendChild(this.boxDiv_); this.boxBorderWidths_ = getBorderWidths(this.boxDiv_); var me = this; this.keyDownListener_ = google.maps.event.addDomListener(document, 'keydown', function (e) { me.onKeyDown_(e); }); this.keyUpListener_ = google.maps.event.addDomListener(document, 'keyup', function (e) { me.onKeyUp_(e); }); this.mouseDownListener_ = google.maps.event.addDomListener(this.paneDiv_, 'mousedown', function (e) { me.onMouseDown_(e); }); this.mouseDownListenerDocument_ = google.maps.event.addDomListener(document, 'mousedown', function (e) { me.onMouseDownDocument_(e); }); this.mouseMoveListener_ = google.maps.event.addDomListener(document, 'mousemove', function (e) { me.onMouseMove_(e); }); this.mouseUpListener_ = google.maps.event.addDomListener(document, 'mouseup', function (e) { me.onMouseUp_(e); }); this.hotKeyDown_ = false; this.dragging_ = false; this.startPt_ = null; this.endPt_ = null; this.boxMaxX_ = null; this.boxMaxY_ = null; this.mousePosn_ = null; this.mapPosn_ = getElementPosition(this.map_.getDiv()); this.mouseDown_ = false; }; /** * Returns true if the hot key is being pressed when an event occurs. * @param {Event} e * @return {Boolean} */ DragZoom.prototype.isHotKeyDown_ = function (e) { var isHot; e = e || window.event; isHot = (e.shiftKey && this.key_ === 'shift') || (e.altKey && this.key_ === 'alt') || (e.ctrlKey && this.key_ === 'ctrl'); if (!isHot) { // Need to look at keyCode for Opera because it // doesn't set the shiftKey, altKey, ctrlKey properties // unless a non-modifier event is being reported. // // See http://cross-browser.com/x/examples/shift_mode.php // Also see http://unixpapa.com/js/key.html switch (e.keyCode) { case 16: if (this.key_ === 'shift') { isHot = true; } break; case 17: if (this.key_ === 'ctrl') { isHot = true; } break; case 18: if (this.key_ === 'alt') { isHot = true; } break; } } return isHot; }; /** * Checks if the mouse is on top of the map. The position is captured * in onMouseMove_. * @return true if mouse is on top of the map div. */ DragZoom.prototype.isMouseOnMap_ = function () { var mousePos = this.mousePosn_; if (mousePos) { var mapPos = this.mapPosn_; var mapDiv = this.map_.getDiv(); return mousePos.left > mapPos.left && mousePos.left < mapPos.left + mapDiv.offsetWidth && mousePos.top > mapPos.top && mousePos.top < mapPos.top + mapDiv.offsetHeight; } else { // if user never moved mouse return false; } }; /** * Show or hide the overlay pane, depending on whether the mouse is over the map. */ DragZoom.prototype.setPaneVisibility_ = function () { if (this.map_ && this.hotKeyDown_ && this.isMouseOnMap_()) { var mapDiv = this.map_.getDiv(); this.paneDiv_.style.left = 0 + 'px'; this.paneDiv_.style.top = 0 + 'px'; this.paneDiv_.style.width = mapDiv.offsetWidth - (this.borderWidths_.left + this.borderWidths_.right) + 'px'; this.paneDiv_.style.height = mapDiv.offsetHeight - (this.borderWidths_.top + this.borderWidths_.bottom) + 'px'; this.paneDiv_.style.display = 'block'; this.boxMaxX_ = parseInt(this.paneDiv_.style.width, 10) - (this.boxBorderWidths_.left + this.boxBorderWidths_.right); this.boxMaxY_ = parseInt(this.paneDiv_.style.height, 10) - (this.boxBorderWidths_.top + this.boxBorderWidths_.bottom); } else { this.paneDiv_.style.display = 'none'; } }; /** * Handle key down. Activate the tool only if the mouse is on top of the map. * @param {Event} e */ DragZoom.prototype.onKeyDown_ = function (e) { var me = this; if (this.map_ && !this.hotKeyDown_ && this.isHotKeyDown_(e)) { //desativa o clique permanente i3GEO.eventos.cliquePerm.desativa(); me.hotKeyDown_ = true; me.setPaneVisibility_(); /** * This event is fired when the hot key is pressed. * @name DragZoom#activate * @event */ google.maps.event.trigger(me, 'activate'); } }; /** * Get the google.maps.Point of the mouse position. * @param {Object} e * @return {google.maps.Point} point * @private */ DragZoom.prototype.getMousePoint_ = function (e) { var mousePosn = getMousePosition(e); var p = new google.maps.Point(); p.x = mousePosn.left - this.mapPosn_.left - this.borderWidths_.left; p.y = mousePosn.top - this.mapPosn_.top - this.borderWidths_.top; p.x = Math.min(p.x, this.boxMaxX_); p.y = Math.min(p.y, this.boxMaxY_); p.x = Math.max(p.x, 0); p.y = Math.max(p.y, 0); return p; }; /** * Handle mouse down. * @param {Event} e */ DragZoom.prototype.onMouseDown_ = function (e) { if (this.map_ && this.hotKeyDown_) { this.mapPosn_ = getElementPosition(this.map_.getDiv()); this.dragging_ = true; this.startPt_ = this.endPt_ = this.getMousePoint_(e); var prj = this.prjov_.getProjection(); var latlng = prj.fromDivPixelToLatLng(this.startPt_); /** * This event is fired when the drag operation begins. * @name DragZoom#dragstart * @param {GLatLng} startLatLng * @event */ google.maps.event.trigger(this, 'dragstart', latlng); } }; /** * Handle mouse down at the document level. * @param {Event} e */ DragZoom.prototype.onMouseDownDocument_ = function (e) { this.mouseDown_ = true; }; /** * Handle mouse move. * @param {Event} e */ DragZoom.prototype.onMouseMove_ = function (e) { this.mousePosn_ = getMousePosition(e); if (this.dragging_) { this.endPt_ = this.getMousePoint_(e); var left = Math.min(this.startPt_.x, this.endPt_.x); var top = Math.min(this.startPt_.y, this.endPt_.y); var width = Math.abs(this.startPt_.x - this.endPt_.x); var height = Math.abs(this.startPt_.y - this.endPt_.y); this.boxDiv_.style.left = left + 'px'; this.boxDiv_.style.top = top + 'px'; this.boxDiv_.style.width = width + 'px'; this.boxDiv_.style.height = height + 'px'; this.boxDiv_.style.display = 'block'; /** * This event is repeatedly fired while the user drags the box. The southwest and northeast * point are passed as parameters of type google.maps.Point (for performance reasons), * relative to the map container. Note: the event listener is responsible * for converting Pixel to LatLng, if necessary. * @name DragZoom#drag * @param {google.maps.Point} southwestPixel * @param {google.maps.Point} northeastPixel * @event */ google.maps.event.trigger(this, 'drag', new google.maps.Point(left, top + height), new google.maps.Point(left + width, top)); } else if (!this.mouseDown_) { this.setPaneVisibility_(); } }; /** * Handle mouse up. * @param {Event} e */ DragZoom.prototype.onMouseUp_ = function (e) { this.mouseDown_ = false; if (this.dragging_) { //desativa o clique permanente i3GEO.eventos.cliquePerm.ativa(); var left = Math.min(this.startPt_.x, this.endPt_.x); var top = Math.min(this.startPt_.y, this.endPt_.y); var width = Math.abs(this.startPt_.x - this.endPt_.x); var height = Math.abs(this.startPt_.y - this.endPt_.y); var prj = this.prjov_.getProjection(); // 2009-05-29: since V3 does not have fromContainerPixel, //needs find offset here var containerPos = getElementPosition(this.map_.getDiv()); var mapPanePos = getElementPosition(this.prjov_.getPanes().mapPane); left = left + (containerPos.left - mapPanePos.left); top = top + (containerPos.top - mapPanePos.top); var sw = prj.fromDivPixelToLatLng(new google.maps.Point(left, top + height)); var ne = prj.fromDivPixelToLatLng(new google.maps.Point(left + width, top)); var bnds = new google.maps.LatLngBounds(sw, ne); this.map_.fitBounds(bnds); this.dragging_ = false; this.boxDiv_.style.display = 'none'; /** * This event is fired when the drag operation ends. * Note that the event is not fired if the hot key is released before the drag operation ends. * @name DragZoom#dragend * @param {GLatLngBounds} newBounds * @event */ google.maps.event.trigger(this, 'dragend', bnds); } }; /** * Handle key up. * @param {Event} e */ DragZoom.prototype.onKeyUp_ = function (e) { if (this.map_ && this.hotKeyDown_) { this.hotKeyDown_ = false; this.dragging_ = false; this.boxDiv_.style.display = 'none'; this.paneDiv_.style.display = "none"; /** * This event is fired while the user release the key * @name DragZoom#deactivate * @event */ google.maps.event.trigger(this, 'deactivate'); } }; /** * @name google.maps.Map * @class These are new methods added to the Google Maps API's * Map * class. */ /** * Enable drag zoom. The user can zoom to an area of interest by holding down the hot key * (shift | ctrl | alt ) while dragging a box around the area. * @param {KeyDragZoomOptions} opt_zoomOpts */ google.maps.Map.prototype.enableKeyDragZoom = function (opt_zoomOpts) { this.dragZoom_ = new DragZoom(this, opt_zoomOpts); }; /** * Disable drag zoom. */ google.maps.Map.prototype.disableKeyDragZoom = function () { var d = this.dragZoom_; if (d) { google.maps.event.removeListener(d.mouseDownListener_); google.maps.event.removeListener(d.mouseDownListenerDocument_); google.maps.event.removeListener(d.mouseMoveListener_); google.maps.event.removeListener(d.mouseUpListener_); google.maps.event.removeListener(d.keyUpListener_); google.maps.event.removeListener(d.keyDownListener_); this.getDiv().removeChild(d.boxDiv_); this.getDiv().removeChild(d.paneDiv_); this.dragZoom_ = null; } }; /** * Returns true if the drag zoom feature has been enabled. * @return {Boolean} */ google.maps.Map.prototype.keyDragZoomEnabled = function () { return this.dragZoom_ !== null; }; /** * Returns the DragZoom object which is created when google.maps.Map.enableKeyDragZoom is called. * With this object you can use google.maps.event.addListener to attach event listeners * for the 'activate', 'deactivate', 'dragstart', 'drag', and 'dragend' events. * @return {DragZoom} */ google.maps.Map.prototype.getDragZoomObject = function () { return this.dragZoom_; }; })();