/** * @fileoverview 百度地图的测距工具类,对外开放。 * 允许用户在地图上点击完成距离的测量。 * 使用者可以自定义测距线段的相关样式,例如线宽、颜色、测距结果所用的单位制等等。 * 主入口类是DistanceTool, * 基于Baidu Map API 1.2。 * * @author Baidu Map Api Group * @version 1.2 */ /** * @namespace BMap的所有library类均放在BMapLib命名空间下 */ (function (root, factory) { if (typeof exports === 'object') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { define(factory); } else { root.BMapLib = root.BMapLib || {}; root.BMapLib.Heatmap = root.BMapLib.DistanceTool || factory(); } })(this, function() { var baidu = baidu || {guid : "$BAIDU$"}; var context = {}; (function() { // 一些页面级别唯一的属性,需要挂载在window[baidu.guid]上 context[baidu.guid] = {}; /** * 将源对象的所有属性拷贝到目标对象中 * @name baidu.extend * @function * @grammar baidu.extend(target, source) * @param {Object} target 目标对象 * @param {Object} source 源对象 * @returns {Object} 目标对象 */ baidu.extend = function (target, source) { for (var p in source) { if (source.hasOwnProperty(p)) { target[p] = source[p]; } } return target; }; /** * @ignore * @namespace * @baidu.lang 对语言层面的封装,包括类型判断、模块扩展、继承基类以及对象自定义事件的支持。 * @property guid 对象的唯一标识 */ baidu.lang = baidu.lang || {}; /** * 返回一个当前页面的唯一标识字符串。 * @function * @grammar baidu.lang.guid() * @returns {String} 当前页面的唯一标识字符串 */ baidu.lang.guid = function() { return "TANGRAM__" + (context[baidu.guid]._counter ++).toString(36); }; context[baidu.guid]._counter = context[baidu.guid]._counter || 1; /** * 所有类的实例的容器 * key为每个实例的guid */ context[baidu.guid]._instances = context[baidu.guid]._instances || {}; /** * Tangram继承机制提供的一个基类,用户可以通过继承baidu.lang.Class来获取它的属性及方法。 * @function * @name baidu.lang.Class * @grammar baidu.lang.Class(guid) * @param {string} guid 对象的唯一标识 * @meta standard * @remark baidu.lang.Class和它的子类的实例均包含一个全局唯一的标识guid。 * guid是在构造函数中生成的,因此,继承自baidu.lang.Class的类应该直接或者间接调用它的构造函数。
* baidu.lang.Class的构造函数中产生guid的方式可以保证guid的唯一性,及每个实例都有一个全局唯一的guid。 */ baidu.lang.Class = function(guid) { this.guid = guid || baidu.lang.guid(); context[baidu.guid]._instances[this.guid] = this; }; context[baidu.guid]._instances = context[baidu.guid]._instances || {}; /** * 判断目标参数是否string类型或String对象 * @name baidu.lang.isString * @function * @grammar baidu.lang.isString(source) * @param {Any} source 目标参数 * @shortcut isString * @meta standard * * @returns {boolean} 类型判断结果 */ baidu.lang.isString = function (source) { return '[object String]' == Object.prototype.toString.call(source); }; /** * 判断目标参数是否为function或Function实例 * @name baidu.lang.isFunction * @function * @grammar baidu.lang.isFunction(source) * @param {Any} source 目标参数 * @returns {boolean} 类型判断结果 */ baidu.lang.isFunction = function (source) { return '[object Function]' == Object.prototype.toString.call(source); }; /** * 重载了默认的toString方法,使得返回信息更加准确一些。 * @return {string} 对象的String表示形式 */ baidu.lang.Class.prototype.toString = function(){ return "[object " + (this._className || "Object" ) + "]"; }; /** * 释放对象所持有的资源,主要是自定义事件。 * @name dispose * @grammar obj.dispose() */ baidu.lang.Class.prototype.dispose = function(){ delete context[baidu.guid]._instances[this.guid]; for(var property in this){ if (!baidu.lang.isFunction(this[property])) { delete this[property]; } } this.disposed = true; }; /** * 自定义的事件对象。 * @function * @name baidu.lang.Event * @grammar baidu.lang.Event(type[, target]) * @param {string} type 事件类型名称。为了方便区分事件和一个普通的方法,事件类型名称必须以"on"(小写)开头。 * @param {Object} [target]触发事件的对象 * @meta standard * @remark 引入该模块,会自动为Class引入3个事件扩展方法:addEventListener、removeEventListener和dispatchEvent。 * @see baidu.lang.Class */ baidu.lang.Event = function (type, target) { this.type = type; this.returnValue = true; this.target = target || null; this.currentTarget = null; }; /** * 注册对象的事件监听器。引入baidu.lang.Event后,Class的子类实例才会获得该方法。 * @grammar obj.addEventListener(type, handler[, key]) * @param {string} type 自定义事件的名称 * @param {Function} handler 自定义事件被触发时应该调用的回调函数 * @param {string} [key] 为事件监听函数指定的名称,可在移除时使用。如果不提供,方法会默认为它生成一个全局唯一的key。 * @remark 事件类型区分大小写。如果自定义事件名称不是以小写"on"开头,该方法会给它加上"on"再进行判断,即"click"和"onclick"会被认为是同一种事件。 */ baidu.lang.Class.prototype.addEventListener = function (type, handler, key) { if (!baidu.lang.isFunction(handler)) { return; } !this.__listeners && (this.__listeners = {}); var t = this.__listeners, id; if (typeof key == "string" && key) { if (/[^\w\-]/.test(key)) { throw("nonstandard key:" + key); } else { handler.hashCode = key; id = key; } } type.indexOf("on") != 0 && (type = "on" + type); typeof t[type] != "object" && (t[type] = {}); id = id || baidu.lang.guid(); handler.hashCode = id; t[type][id] = handler; }; /** * 移除对象的事件监听器。引入baidu.lang.Event后,Class的子类实例才会获得该方法。 * @grammar obj.removeEventListener(type, handler) * @param {string} type 事件类型 * @param {Function|string} handler 要移除的事件监听函数或者监听函数的key * @remark 如果第二个参数handler没有被绑定到对应的自定义事件中,什么也不做。 */ baidu.lang.Class.prototype.removeEventListener = function (type, handler) { if (baidu.lang.isFunction(handler)) { handler = handler.hashCode; } else if (!baidu.lang.isString(handler)) { return; } !this.__listeners && (this.__listeners = {}); type.indexOf("on") != 0 && (type = "on" + type); var t = this.__listeners; if (!t[type]) { return; } t[type][handler] && delete t[type][handler]; }; /** * 派发自定义事件,使得绑定到自定义事件上面的函数都会被执行。引入baidu.lang.Event后,Class的子类实例才会获得该方法。 * @grammar obj.dispatchEvent(event, options) * @param {baidu.lang.Event|String} event Event对象,或事件名称(1.1.1起支持) * @param {Object} options 扩展参数,所含属性键值会扩展到Event对象上(1.2起支持) * @remark 处理会调用通过addEventListenr绑定的自定义事件回调函数之外,还会调用直接绑定到对象上面的自定义事件。 * 例如:
* myobj.onMyEvent = function(){}
* myobj.addEventListener("onMyEvent", function(){}); */ baidu.lang.Class.prototype.dispatchEvent = function (event, options) { if (baidu.lang.isString(event)) { event = new baidu.lang.Event(event); } !this.__listeners && (this.__listeners = {}); options = options || {}; for (var i in options) { event[i] = options[i]; } var i, t = this.__listeners, p = event.type; event.target = event.target || this; event.currentTarget = this; p.indexOf("on") != 0 && (p = "on" + p); baidu.lang.isFunction(this[p]) && this[p].apply(this, arguments); if (typeof t[p] == "object") { for (i in t[p]) { t[p][i].apply(this, arguments); } } return event.returnValue; }; /** * 为类型构造器建立继承关系 * @name baidu.lang.inherits * @function * @grammar baidu.lang.inherits(subClass, superClass[, className]) * @param {Function} subClass 子类构造器 * @param {Function} superClass 父类构造器 * @param {string} className 类名标识 * @remark 使subClass继承superClass的prototype, * 因此subClass的实例能够使用superClass的prototype中定义的所有属性和方法。
* 这个函数实际上是建立了subClass和superClass的原型链集成,并对subClass进行了constructor修正。
* 注意:如果要继承构造函数,需要在subClass里面call一下,具体见下面的demo例子 * @shortcut inherits * @meta standard * @see baidu.lang.Class */ baidu.lang.inherits = function (subClass, superClass, className) { var key, proto, selfProps = subClass.prototype, clazz = new Function(); clazz.prototype = superClass.prototype; proto = subClass.prototype = new clazz(); for (key in selfProps) { proto[key] = selfProps[key]; } subClass.prototype.constructor = subClass; subClass.superClass = superClass.prototype; if ("string" == typeof className) { proto._className = className; } }; /** * @ignore * @namespace baidu.dom 操作dom的方法。 */ baidu.dom = baidu.dom || {}; /** * 从文档中获取指定的DOM元素 * * @param {string|HTMLElement} id 元素的id或DOM元素 * @meta standard * @return {HTMLElement} DOM元素,如果不存在,返回null,如果参数不合法,直接返回参数 */ baidu._g = baidu.dom._g = function (id) { if (baidu.lang.isString(id)) { return document.getElementById(id); } return id; }; /** * 从文档中获取指定的DOM元素 * @name baidu.dom.g * @function * @grammar baidu.dom.g(id) * @param {string|HTMLElement} id 元素的id或DOM元素 * @meta standard * * @returns {HTMLElement|null} 获取的元素,查找不到时返回null,如果参数不合法,直接返回参数 */ baidu.g = baidu.dom.g = function (id) { if ('string' == typeof id || id instanceof String) { return document.getElementById(id); } else if (id && id.nodeName && (id.nodeType == 1 || id.nodeType == 9)) { return id; } return null; }; /** * 在目标元素的指定位置插入HTML代码 * @name baidu.dom.insertHTML * @function * @grammar baidu.dom.insertHTML(element, position, html) * @param {HTMLElement|string} element 目标元素或目标元素的id * @param {string} position 插入html的位置信息,取值为beforeBegin,afterBegin,beforeEnd,afterEnd * @param {string} html 要插入的html * @remark * * 对于position参数,大小写不敏感
* 参数的意思:beforeBegin<span>afterBegin this is span! beforeEnd</span> afterEnd
* 此外,如果使用本函数插入带有script标签的HTML字符串,script标签对应的脚本将不会被执行。 * * @shortcut insertHTML * @meta standard * * @returns {HTMLElement} 目标元素 */ baidu.insertHTML = baidu.dom.insertHTML = function (element, position, html) { element = baidu.dom.g(element); var range,begin; if (element.insertAdjacentHTML) { element.insertAdjacentHTML(position, html); } else { // 这里不做"undefined" != typeof(HTMLElement) && !window.opera判断,其它浏览器将出错?! // 但是其实做了判断,其它浏览器下等于这个函数就不能执行了 range = element.ownerDocument.createRange(); // FF下range的位置设置错误可能导致创建出来的fragment在插入dom树之后html结构乱掉 // 改用range.insertNode来插入html, by wenyuxiang @ 2010-12-14. position = position.toUpperCase(); if (position == 'AFTERBEGIN' || position == 'BEFOREEND') { range.selectNodeContents(element); range.collapse(position == 'AFTERBEGIN'); } else { begin = position == 'BEFOREBEGIN'; range[begin ? 'setStartBefore' : 'setEndAfter'](element); range.collapse(begin); } range.insertNode(range.createContextualFragment(html)); } return element; }; /** * 为目标元素添加className * @name baidu.dom.addClass * @function * @grammar baidu.dom.addClass(element, className) * @param {HTMLElement|string} element 目标元素或目标元素的id * @param {string} className 要添加的className,允许同时添加多个class,中间使用空白符分隔 * @remark * 使用者应保证提供的className合法性,不应包含不合法字符,className合法字符参考:http://www.w3.org/TR/CSS2/syndata.html。 * @shortcut addClass * @meta standard * * @returns {HTMLElement} 目标元素 */ baidu.ac = baidu.dom.addClass = function (element, className) { element = baidu.dom.g(element); var classArray = className.split(/\s+/), result = element.className, classMatch = " " + result + " ", i = 0, l = classArray.length; for (; i < l; i++){ if ( classMatch.indexOf( " " + classArray[i] + " " ) < 0 ) { result += (result ? ' ' : '') + classArray[i]; } } element.className = result; return element; }; /** * @ignore * @namespace baidu.event 屏蔽浏览器差异性的事件封装。 * @property target 事件的触发元素 * @property pageX 鼠标事件的鼠标x坐标 * @property pageY 鼠标事件的鼠标y坐标 * @property keyCode 键盘事件的键值 */ baidu.event = baidu.event || {}; /** * 事件监听器的存储表 * @private * @meta standard */ baidu.event._listeners = baidu.event._listeners || []; /** * 为目标元素添加事件监听器 * @name baidu.event.on * @function * @grammar baidu.event.on(element, type, listener) * @param {HTMLElement|string|window} element 目标元素或目标元素id * @param {string} type 事件类型 * @param {Function} listener 需要添加的监听器 * @remark * 1. 不支持跨浏览器的鼠标滚轮事件监听器添加
* 2. 改方法不为监听器灌入事件对象,以防止跨iframe事件挂载的事件对象获取失败 * @shortcut on * @meta standard * @see baidu.event.un * * @returns {HTMLElement|window} 目标元素 */ baidu.on = baidu.event.on = function (element, type, listener) { type = type.replace(/^on/i, ''); element = baidu._g(element); var realListener = function (ev) { // 1. 这里不支持EventArgument, 原因是跨frame的事件挂载 // 2. element是为了修正this listener.call(element, ev); }, lis = baidu.event._listeners, filter = baidu.event._eventFilter, afterFilter, realType = type; type = type.toLowerCase(); // filter过滤 if(filter && filter[type]){ afterFilter = filter[type](element, type, realListener); realType = afterFilter.type; realListener = afterFilter.listener; } // 事件监听器挂载 if (element.addEventListener) { element.addEventListener(realType, realListener, false); } else if (element.attachEvent) { element.attachEvent('on' + realType, realListener); } // 将监听器存储到数组中 lis[lis.length] = [element, type, listener, realListener, realType]; return element; }; /** * 为目标元素移除事件监听器 * @name baidu.event.un * @function * @grammar baidu.event.un(element, type, listener) * @param {HTMLElement|string|window} element 目标元素或目标元素id * @param {string} type 事件类型 * @param {Function} listener 需要移除的监听器 * @shortcut un * @meta standard * * @returns {HTMLElement|window} 目标元素 */ baidu.un = baidu.event.un = function (element, type, listener) { element = baidu._g(element); type = type.replace(/^on/i, '').toLowerCase(); var lis = baidu.event._listeners, len = lis.length, isRemoveAll = !listener, item, realType, realListener; //如果将listener的结构改成json //可以节省掉这个循环,优化性能 //但是由于un的使用频率并不高,同时在listener不多的时候 //遍历数组的性能消耗不会对代码产生影响 //暂不考虑此优化 while (len--) { item = lis[len]; // listener存在时,移除element的所有以listener监听的type类型事件 // listener不存在时,移除element的所有type类型事件 if (item[1] === type && item[0] === element && (isRemoveAll || item[2] === listener)) { realType = item[4]; realListener = item[3]; if (element.removeEventListener) { element.removeEventListener(realType, realListener, false); } else if (element.detachEvent) { element.detachEvent('on' + realType, realListener); } lis.splice(len, 1); } } return element; }; /** * 阻止事件的默认行为 * @name baidu.event.preventDefault * @function * @grammar baidu.event.preventDefault(event) * @param {Event} event 事件对象 * @meta standard */ baidu.preventDefault = baidu.event.preventDefault = function (event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } }; })(); /** * @exports DistanceTool as BMapLib.DistanceTool */ var DistanceTool = function(map, opts){ if (!map) { return; } /** * OperationMask,透明覆盖层,在地图上进行鼠标绘制操作时使用 */ var OperationMask = this.OperationMask = { /** * map对象 * @type {Map} */ _map : null, /** * HTML字符串 * @type {String} */ _html : "
", /** * html元素 * @type {HTMLElement} */ _maskElement : null, /** * 鼠标指针 * @type {String} */ _cursor: 'default', /** * 操作层是否在使用中 * @type {Boolean} */ _inUse: false, /** * 透明覆盖层的显示 * * @param {Map} map map对象 * @return 无返回值 */ show : function(map) { if (!this._map) { this._map = map; } this._inUse = true; if (!this._maskElement) { this._createMask(map); } this._maskElement.style.display = 'block'; }, /** * 创建覆盖层 * * @param {Map} map map对象 * @return 无返回值 */ _createMask : function(map) { this._map = map; if (!this._map) { return; } baidu.insertHTML(this._map.getContainer(), "beforeEnd", this._html); var elem = this._maskElement = this._map.getContainer().lastChild; var stopAndPrevent = function(e) { stopBubble(e); return baidu.preventDefault(e); } baidu.on(elem, 'mouseup', function(e) { if (e.button == 2) { stopAndPrevent(e); } }); baidu.on(elem, 'contextmenu', stopAndPrevent); elem.style.display = 'none'; }, /** * 获取当前绘制点的地理坐标 * * @param {Event} e e对象 * @param {Boolean} n 是否向上查到相对于地图container元素的坐标位置 * @return Point对象的位置信息 */ getDrawPoint : function(e, n) { e = global.event || e; var x = e.layerX || e.offsetX || 0; var y = e.layerY || e.offsetY || 0; var t = e.target || e.srcElement; if (t != OperationMask.getDom(this._map) && n == true) { while (t && t != this._map.getContainer()) { if (!(t.clientWidth == 0 && t.clientHeight == 0 && t.offsetParent && t.offsetParent.nodeName.toLowerCase() == 'td')) { x += t.offsetLeft; y += t.offsetTop; } t = t.offsetParent; } } if (t != OperationMask.getDom(this._map) && t != this._map.getContainer()) { return; } if (typeof x === 'undefined' || typeof y === 'undefined') { return; } if (isNaN(x) || isNaN(y)) { return; } return this._map.pixelToPoint(new BMap.Pixel(x, y)); }, /** * 透明覆盖层的隐藏 * * @return 无返回值 */ hide : function() { if (!this._map) { return; } this._inUse = false; if (this._maskElement) { this._maskElement.style.display = 'none'; } }, /** * 获取HTML容器 * * @param {Map} map map对象 * @return HTML容器元素 */ getDom : function(map) { if (!this._maskElement) { this._createMask(map); } return this._maskElement; }, /** * 设置鼠标样式 * * @type {String} cursor 鼠标样式 * @return 无返回值 */ _setCursor : function(cursor) { this._cursor = cursor || 'default'; if (this._maskElement) { this._maskElement.style.cursor = this._cursor; } } }; /** * map对象 * @private * @type {Map} */ this._map = map; opts = opts || {}; /** * _opts是默认参数赋值。 * 下面通过用户输入的opts,对默认参数赋值 * @private * @type {Json} */ this._opts = baidu.extend( baidu.extend(this._opts || {}, { /** * 测距提示 * @private * @type {String} */ tips : "测距", /** * 测距过程中,提示框文字 * @private * @type {String} */ followText : "单击确定地点,双击结束", /** * 测距结果所用的单位制,可接受的属性为"metric"表示米制和"us"表示美国传统单位 * @private * @type {String} */ unit : "metric", /** * 折线颜色 * @private * @type {String} */ lineColor : "#ff6319", /** * 折线线宽 * @private * @type {Number} */ lineStroke : 2, /** * 折线透明度 * @private * @type {Number} */ opacity : 0.8, /** * 折线样式 * @private * @type {String} */ lineStyle : "solid", /** * 跟随鼠标样式 * @private * @type {String} */ cursor : "http://api.map.baidu.com/images/ruler.cur", /** * 转折点的ICON样式 * @private * @type {BMap.Icon} */ secIcon : null, /** * 转折点的ICON样式 * @private * @type {BMap.Icon} */ closeIcon : null }) , opts); /** * 跟随的title覆盖物 * @private * @type {BMap.Label} */ this._followTitle = null; /** * 折线包含所有点的数组 * @private * @type {Array} */ this._points = []; /** * 折线所包含的所有path数组 * @private * @type {Array} */ this._paths = []; /** * 折线结点图片数组 * @private * @type {Array} */ this._dots = []; /** * 折线测距包含所有线段的距离 * @private * @type {Array} */ this._segDistance = []; /** * 覆盖物的数组 * @private * @type {Array} */ this._overlays = []; /** * 是否在调用map.clearOverlays清除画线需要建立的相关overlay元素 * @private * @type {Boolean} */ this._enableMassClear = true, /** * 单位制,存储语言包中定义的单位名称 * @private * @type {Json} */ this._units = { // metric 表示米制 metric : { /** * 米制的名称 * @type {String} */ name : "metric", /** * 和米制的换算关系 * @type {Number} */ conv : 1, /** * 米制单位下两个单位制之间的换算关系 * @type {Number} */ incon : 1000, /** * 米制单位下较小单位 * @type {String} */ u1 : "米", /** * 米制单位下较大单位 * @type {String} */ u2 : "公里" }, // us 表示美国传统单位,各参数意义同上metric us : { name : "us", conv : 3.2808, incon : 5279.856, u1 : "英尺", u2 : "英里" } }; /** * 是否已经开启了测距状态 * @private * @type {Boolean} */ this._isOpen = false; /** * 未点击任何一点时,鼠标移动时的跟随提示文字 * @private * @type {String} */ this._startFollowText = "单击确定起点"; /** * 地图移动的计时器 * @private * @type {Object} */ this._movingTimerId = null; /** * 测距需要添加的CSS样式 * @private * @type {Json} */ this._styles = { "BMapLib_diso" : "height:17px;width:5px;position:absolute;background:url(http://api.map.baidu.com/images/dis_box_01.gif) no-repeat left top" ,"BMapLib_disi" : "color:#7a7a7a;position:absolute;left:5px;padding:0 4px 1px 0;line-height:17px;background:url(http://api.map.baidu.com/images/dis_box_01.gif) no-repeat right top" ,"BMapLib_disBoxDis" : "color:#ff6319;font-weight:bold" }; if (this._opts.lineStroke <= 0) { this._opts.lineStroke = 2; } if (this._opts.opacity > 1) { this._opts.opacity = 1; } else if (this._opts.opacity < 0) { this._opts.opacity = 0; } if (this._opts.lineStyle != "solid" && this._opts.lineStyle != "dashed") { this._opts.lineStyle = "solid"; } if (!this._units[this._opts.unit]) { this._opts.unit = "metric"; } this.text = "测距"; } // 通过baidu.lang下的inherits方法,让DistanceTool继承baidu.lang.Class baidu.lang.inherits(DistanceTool, baidu.lang.Class, "DistanceTool"); /** * 地图区域的移动事件绑定 * @return 无返回值 */ DistanceTool.prototype._bind = function(){ // 设置鼠标样式 this._setCursor(this._opts.cursor); var me = this; var OperationMask = this.OperationMask; // 在装载地图的页面元素上,绑定鼠标移动事件 baidu.on(this._map.getContainer(), "mousemove", function(e){ if (!me._isOpen) { return; } if (!me._followTitle) { return; } e = global.event || e; var t = e.target || e.srcElement; // 如果触发该事件的页面元素不是遮盖效果层,则返回,无操作 if (t != OperationMask.getDom(me._map)) { me._followTitle.hide(); return; } if (!me._mapMoving) { me._followTitle.show(); } // 设置鼠标移动过程中,跟随的文字提示框的位置 var pt = OperationMask.getDrawPoint(e, true); me._followTitle.setPosition(pt); }); // 创建鼠标跟随的文字提示框 if (this._startFollowText) { var t = this._followTitle = new BMap.Label(this._startFollowText, {offset : new BMap.Size(14, 16)}); this._followTitle.setStyles({color : "#333", borderColor : "#ff0103"}); } }; /** * 开启地图的测距状态 * @return {Boolean},开启测距状态成功,返回true;否则返回false。 * * @example 参考示例:
* myDistanceToolObject.open(); */ DistanceTool.prototype.open = function(){ var OperationMask = this.OperationMask; // 判断测距状态是否已经开启 if (this._isOpen == true){ return true; } // 已有其他地图上的鼠标操作工具开启 if (DistanceTool._toolInUse) { return; } else { this._isOpen = true; DistanceTool._toolInUse = true; } // 判断是否是否在移动过程中 if (this._mapMoving){ delete this._mapMoving; } var me = this; // 增加鼠标在地图区域移动的事件 // 通过binded参数,避免多次绑定 if (!this._binded) { this._binded = true; // 绑定控件项事件 this._bind(); // 地图的移动过程中,需要隐藏相关的提示框 this._map.addEventListener("moving", function(){ me._hideCurrent(); }); } // 将文字提示框作为BMap.Label元素,提交给Map Api进行管理 if (this._followTitle) { this._map.addOverlay(this._followTitle); this._followTitle.hide(); } /** * 测距过程中,点击地图时,触发的操作 * @ignore * @param {Object} e event对象 */ var distClick = function(e) { var map = me._map; if (!me._isOpen) { return; } // 通过event对象,计算得出点击位置的物理坐标,poi为一个BMap.Point对象 e = global.event || e; var poi = OperationMask.getDrawPoint(e, true); // 验证计算得出的该点的位置合理性 if (!me._isPointValid(poi)) { return; } // 记录当前点的屏幕位置 me._bind.initX = e.pageX || e.clientX || 0; me._bind.initY = e.pageY || e.clientY || 0; // 这个if循环内的计算是,判断当前这个点,与存储内的最后一个点的距离, // 如果距离过小,比如小于5,可以认为是用户的误点,可以忽略掉 if (me._points.length > 0){ var lstPx = map.pointToPixel(me._points[me._points.length - 1]); var thisPx = map.pointToPixel(poi); var dis = Math.sqrt(Math.pow(lstPx.x - thisPx.x, 2) + Math.pow(lstPx.y - thisPx.y, 2)); if (dis < 5) { return; } } me._bind.x = e.layerX || e.offsetX || 0; me._bind.y = e.layerY || e.offsetY || 0; me._points.push(poi); // 添加测距结点 me._addSecPoint(poi); // 调整跟踪鼠标的标签 if (me._paths.length == 0) { me._formatTitle(1, me._opts.followText, me._getTotalDistance()); } // 修改确定线的颜色 if (me._paths.length > 0) { me._paths[me._paths.length - 1].show(); me._paths[me._paths.length - 1].setStrokeOpacity(me._opts.opacity); } var path = new BMap.Polyline([poi, poi], {enableMassClear : me._enableMassClear}); me._map.addOverlay(path); me._paths.push(path); me._overlays.push(path); // 测距模式下线样式固定 path.setStrokeWeight(me._opts.lineStroke); path.setStrokeColor(me._opts.lineColor); path.setStrokeOpacity(me._opts.opacity / 2); path.setStrokeStyle(me._opts.lineStyle); // 如果地图正在移动则隐藏掉 if (me._mapMoving){ path.hide(); } if (me._points.length > 1) { var siblingPath = me._paths[me._points.length - 2]; siblingPath.setPositionAt(1, poi); } // 生成节点旁边的距离显示框 var disText = ""; if (me._points.length > 1) { // 非起点的节点,显示当前的距离 var segDis = me._setSegDistance(me._points[me._points.length - 2], me._points[me._points.length - 1]); var meters = me._getTotalDistance(); disText = me._formatDisStr(meters); } else { disText = "起点"; } var disLabel = new BMap.Label(disText, {offset : new BMap.Size(10, -5), enableMassClear : me._enableMassClear}); disLabel.setStyles({color : "#333", borderColor : "#ff0103"}); me._map.addOverlay(disLabel); me._formatSegLabel(disLabel, disText); me._overlays.push(disLabel); poi.disLabel = disLabel; disLabel.setPosition(poi); /** * 测距过程中,每次点击底图添加节点时,派发事件的接口 * @name DistanceTool#onaddpoint * @event * @param {Event Object} e 回调函数会返回event参数,包括以下返回值: *
{"point : {BMap.Point} 最新添加上的节点BMap.Point对象, *
"pixel:{BMap.pixel} 最新添加上的节点BMap.Pixel对象, *
"index:{Number} 最新添加的节点的索引, *
"distance:{Number} 截止最新添加的节点的总距离} * * @example 参考示例:
* myDistanceToolObject.addEventListener("addpoint", function(e) { alert(e.distance); }); */ // 生成名为onaddpoint的baidu.lang.Event对象 // 并给该event对象添加上point、pixel、index和distance等属性字段 // 然后在此刻,将绑定在onaddpoint上事件,全部赋予event参数,然后派发出去 var event = new baidu.lang.Event("onaddpoint"); event.point = poi; event.pixel = me._map.pointToPixel(poi); event.index = me._points.length - 1; event.distance = me._getTotalDistance().toFixed(0); me.dispatchEvent(event); }; /** * 测距过程中,鼠标在地图上移动时,触发的操作 * @ignore * @param {Object} e event对象 */ var distMove = function(e) { if (!me._isOpen){ return; } // 通过判断数组me._paths的长度,判断当前是否已经有测量节点 // 也就是,如果没有节点,则还没有开始测量 if (me._paths.length > 0) { // 通过event参数,计算当前点的位置 e = global.event || e; var curX = e.pageX || e.clientX || 0; var curY = e.pageY || e.clientY || 0; if (typeof me._bind.initX == "undefined") { me._bind.x = e.layerX || e.offsetX || 0; me._bind.y = e.layerY || e.offsetY || 0; me._bind.initX = curX; me._bind.initY = curY; } var x = me._bind.x + curX - me._bind.initX; var y = me._bind.y + curY - me._bind.initY; // 修改最后一条折线的终点位置,使之随着鼠标移动画线 var path = me._paths[me._paths.length - 1]; var poi = me._map.pixelToPoint(new BMap.Pixel(x, y)); path.setPositionAt(1, poi); if (!me._mapMoving) { path.show(); } var dx = 0; var dy = 0; // 计算当前鼠标位置,是否靠近边界、或者已经出了边界 // 如果在边界位置,则需要向对应的方向移动地图,来进行测量 // 每次移动的距离,设定为8 if (x < 10) { dx = 8; } else if (x > me._map.getSize().width - 10){ dx = -8; } if (y < 10) { dy = 8; } else if (y > me._map.getSize().height - 10){ dy = -8; } // 如果dx和dy都等于0,表明不需要移动地图 if (dx != 0 || dy != 0){ // 此时需要向一个方向,平移地图 if (!distMove._movingTimerId){ me._mapMoving = true; me._map.panBy(dx, dy, {noAnimation : true}); me._movingTimerId = distMove._movingTimerId = setInterval(function(){ me._map.panBy(dx, dy, {noAnimation : true}); }, 30); // 地图移动过程中,隐藏线段和标签 path.hide(); me._followTitle && me._followTitle.hide(); } } else { if (distMove._movingTimerId) { // 此时用户不在需要移动地图来测量,可以清除计时器 clearInterval(distMove._movingTimerId); delete distMove._movingTimerId; delete me._movingTimerId; // 显示跟随提示框,并修改线路位置 var lstP = me._paths[me._paths.length - 1]; var poiN = me._map.pixelToPoint(new BMap.Pixel(x, y)); if (!lstP) { return; } lstP.setPositionAt(1, poiN); lstP.show(); if (me._followTitle) { me._followTitle.setPosition(poiN); me._followTitle.show(); } me._bind.i = 0; me._bind.j = 0; delete me._mapMoving; } } // 实时更新文字提示框中的距离 if (me._followTitle) { var td = me._getTotalDistance(); var dis = me._map.getDistance(me._points[me._points.length - 1], poi); me._updateInstDis(me._followTitle, td + dis); } } else { // 此时用户还没有开始测量,只是鼠标随便在地图上移动 if (me._followTitle) { me._followTitle.show(); e = global.event || e; var t = e.target || e.srcElement; if (t != OperationMask.getDom()) { me._followTitle.hide(); } } } }; /** * 测距要结束时,双击地图,触发的操作 * @ignore * @param {Object} e event对象 */ var distDblclick = function(e) { if (!me._isOpen) { return; } // 结束时,删除绑定的事件 baidu.un(OperationMask.getDom(me._map), "click", distClick); baidu.un(document, "mousemove", distMove); baidu.un(OperationMask.getDom(me._map), "dblclick", distDblclick); baidu.un(document, "keydown", distKeyDown); baidu.un(OperationMask.getDom(me._map), "mouseup", distMouseUp); // 调用close()关闭测距状态 setTimeout(function(){ me.close(); }, 50); }; /** * 测距时的键盘操作 * @ignore * @param {Object} e event对象 */ var distKeyDown = function(e){ e = global.event || e; if (e.keyCode == 27){ // [ESC]退出本次测距 me._clearCurData(); setTimeout(function(){ me.close(); }, 50); } }; /** * 测距过程中,鼠标弹起时,触发的操作 * @ignore * @param {Object} e event对象 */ var distMouseUp = function(e) { e = global.event || e; var ieVersion = 0; if (/msie (\d+\.\d)/i.test(navigator.userAgent)) { ieVersion = document.documentMode || + RegExp['\x241']; } if (ieVersion && e.button != 1 || e.button == 2){ me.close(); } }; // 初始化存储数据 me._initData(); // 调整title的内容 this._formatTitle(); // 创建透明覆盖层,并设置鼠标样式 OperationMask.show(this._map); this._setCursor(this._opts.cursor); // 绑定全部事件 baidu.on(OperationMask.getDom(this._map), "click", distClick); baidu.on(document, "mousemove", distMove); baidu.on(OperationMask.getDom(this._map), "dblclick", distDblclick); baidu.on(document, "keydown", distKeyDown); baidu.on(OperationMask.getDom(this._map), "mouseup", distMouseUp); // 将绑定的事件、和对应的绑定对象,记录在数组中 this.bindFunc = [ {elem : OperationMask.getDom(this._map), type : "click", func : distClick}, {elem : OperationMask.getDom(this._map), type : "dblclick", func : distDblclick}, {elem : document, type : "mousemove", func : distMove}, {elem : document, type : "keydown", func : distKeyDown}, {elem : OperationMask.getDom(this._map), type : "mouseup", func : distMouseUp}]; return true; }; /** * 画线结束时,派发drawend事件 * @return 无返回值 */ DistanceTool.prototype._dispatchLastEvent = function() { /** * 测距时,每次双击底图结束当前测距折线时,派发事件的接口 * @name DistanceTool#ondrawend * @event * @param {Event Object} e 回调函数会返回event参数,包括以下返回值: *
{"points : {BMap.Point} 所有测量时,打下的节点BMap.Point对象, *
"overlays:{Array} 所有测量时,生成的线段BMap.Overlay对象, *
"distance:{Number} 测量解释时的最终距离} * * @example 参考示例:
* myDistanceToolObject.addEventListener("drawend", function(e) { alert(e.distance); }); */ // 生成名为ondrawend的baidu.lang.Event对象 // 并给该event对象添加上points、overlays和distance等属性字段 // 然后在此刻,将绑定在ondrawend上事件,全部赋予event参数,然后派发出去 var event = new baidu.lang.Event("ondrawend"); event.points = this._points ? this._points.slice(0) : []; event.overlays = this._paths ? this._paths.slice(0, this._paths.length - 1) : []; event.distance = this._getTotalDistance().toFixed(0); this.dispatchEvent(event); }; /** * 关闭测距状态 * @return 无返回值 * * @example 参考示例:
* myDistanceToolObject.close(); */ DistanceTool.prototype.close = function(){ var OperationMask = this.OperationMask; if (this._isOpen == false){ return; } this._isOpen = false; DistanceTool._toolInUse = false; if (this._mapMoving){ delete this._mapMoving; } var me = this; me._dispatchLastEvent(); if (me._points.length < 2){ // 不是有效绘制,清除所有内容 me._clearCurData(); } else { me._paths[me._paths.length - 1].remove(); me._paths[me._paths.length - 1] = null; me._paths.length = me._paths.length - 1; // 移除最近一次标记 var pt = me._points[me._points.length - 1]; if (pt.disLabel){ pt.disLabel.remove(); } me._processLastOp(); } OperationMask.hide(); // 删除绑定的事件 for (var i = 0, l = this.bindFunc.length; i < l; i ++){ baidu.un(this.bindFunc[i].elem, this.bindFunc[i].type, this.bindFunc[i].func); } // 停止地图移动 if (me._movingTimerId){ clearInterval(me._movingTimerId); me._movingTimerId = null; } if (this._followTitle){ this._followTitle.hide(); } }; /** * 清除本次测距的暂存数据 * @return 无返回值 */ DistanceTool.prototype._clearCurData = function(){ var OperationMask = this.OperationMask; for (var i = 0, l = this._points.length; i < l; i ++){ if (this._points[i].disLabel){ this._points[i].disLabel.remove(); } } for (var i = 0, l = this._paths.length; i < l; i ++){ this._paths[i].remove(); } for (var i = 0, l = this._dots.length; i < l; i ++){ this._dots[i].remove(); } this._initData(); }; /** * 初始化存储数组 * @return 无返回值 */ DistanceTool.prototype._initData = function(){ // 初始化point数组 this._points.length = 0; // 初始化path数组 this._paths.length = 0; // 初始化分段距离数组 this._segDistance.length = 0; // 初始化结点图像数组 this._dots.length = 0; }; /** * 计算两点之间距离并存放在分段距离数组中 * @param {Point} * @param {Point} * @return {Number} 两个地理点之间的距离 */ DistanceTool.prototype._setSegDistance = function(pt0, pt1){ if (!pt0 || !pt1){ return; } var dis = this._map.getDistance(pt0, pt1); this._segDistance.push(dis); return dis; }; /** * 获得总距离 * @return {Number} 总距离 */ DistanceTool.prototype._getTotalDistance = function(){ var totalDis = 0; for (var i = 0, l = this._segDistance.length; i < l; i ++){ totalDis += this._segDistance[i]; } return totalDis; }; /** * 将米制单位的数值换算成为目标单位下的数值 * @type {Number} 需要转换的数值 * @type {String} 字符串描述的目标单位, * "metric" 表示米制单位, * "us" 表示美国传统单位制 * @return {Number} 转换后的数值 */ DistanceTool.prototype._convertUnit = function(num, unit){ unit = unit || "metric"; if (this._units[unit]){ return num * this._units[unit].conv; } return num; }; /** * 添加测距结点 * @param {BMap.Point} 节点 * @return 无返回值 */ DistanceTool.prototype._addSecPoint = function(pt){ var ico = this._opts.secIcon ? this._opts.secIcon : new BMap.Icon("http://api.map.baidu.com/images/mapctrls.png", new BMap.Size(11, 11), {imageOffset: new BMap.Size(-26, -313)}); var secPt = new BMap.Marker(pt, { icon : ico, clickable : false, baseZIndex : 3500000, zIndexFixed : true, enableMassClear : this._enableMassClear }); this._map.addOverlay(secPt); this._dots.push(secPt); }; /** * 格式化距离字符串 * @param {Number} 距离 * @return {String} 格式化的字符串 */ DistanceTool.prototype._formatDisStr = function(distance){ var u = this._opts.unit; var unit = this._units[u].u1; var dis = this._convertUnit(distance, u); if (dis > this._units[u].incon){ dis = dis / this._units[u].incon; unit = this._units[u].u2; dis = dis.toFixed(1); } else { dis = dis.toFixed(0); } return dis + unit; }; /** * 设置鼠标样式 * @param {String} cursor 鼠标样式 * @return 没有返回值 */ DistanceTool.prototype._setCursor = function(cursor){ var OperationMask = this.OperationMask; // 由于webkit内核浏览器下,cursor设置后默认不会居中,所以需要对偏移值进行设置 var csr = /webkit/.test(navigator.userAgent.toLowerCase()) ? "url(" + this._opts.cursor + ") 3 6, crosshair" : "url(" + this._opts.cursor + "), crosshair" OperationMask._setCursor(csr); }; /** * 获取鼠标样式 * @return {String} 跟随的鼠标样式 */ DistanceTool.prototype._getCursor = function(){ return this._opts.cursor; }; /** * 调整分段距离样式 * @param {BMap.Label} label 提示框的Label * @param {String} 需要填入的文字 * @return 没有返回值 */ DistanceTool.prototype._formatSegLabel = function(label, text){ label.setStyle({"border" : "none", "padding" : "0"}); label.setContent("" + text + ""); }; /** * 处理最后一次操作,当用户双击或测距被强行退出时调用 * @return 没有返回值 */ DistanceTool.prototype._processLastOp = function() { var me = this; // 删除上次移动临时数据 delete me._bind.x; delete me._bind.y; delete me._bind.initX; delete me._bind.initY; // 验证路径 if (me._paths.length > me._points.length - 1){ var l = me._paths.length - 1; me._paths[l].remove(); me._paths[l] = null; me._paths.length = l; } // 保存本次测距对象 var disObj = {}; disObj.points = me._points.slice(0); disObj.paths = me._paths.slice(0); disObj.dots = me._dots.slice(0); disObj.segDis = me._segDistance.slice(0); // 判断总距离和按钮位置 var lstPx = me._map.pointToPixel(disObj.points[disObj.points.length - 1]); var prePx = me._map.pointToPixel(disObj.points[disObj.points.length - 2]); var btnOffset = [0, 0]; var disOffset = [0, 0]; if (lstPx.y - prePx.y >= 0){ // 距离位于下端 disOffset = [-5, 11]; } else { // 距离位于上端 disOffset = [-5, -35]; } if (lstPx.x - prePx.x >= 0){ // 按钮位于右侧 btnOffset = [14, 0]; } else { // 按钮位于左侧 btnOffset = [-14, 0]; } // 显示总距离 var pt = disObj.points[disObj.points.length - 1]; pt.disLabel = new BMap.Label("", {offset: new BMap.Size(-15, -40), enableMassClear: me._enableMassClear}); pt.disLabel.setStyles({color: "#333", borderColor: "#ff0103"}); me._map.addOverlay(pt.disLabel); pt.disLabel.setOffset(new BMap.Size(disOffset[0], disOffset[1])); pt.disLabel.setPosition(pt); me._formatTitle(2, "", "", pt.disLabel); // 添加关闭按钮 var bico = this._opts.closeIcon ? this._opts.closeIcon : new BMap.Icon("http://api.map.baidu.com/images/mapctrls.gif", new BMap.Size(12, 12), {imageOffset: new BMap.Size(0, -14)}); disObj.closeBtn = new BMap.Marker(disObj.points[disObj.points.length - 1], {icon : bico, offset : new BMap.Size(btnOffset[0], btnOffset[1]), baseZIndex : 3600000, enableMassClear : me._enableMassClear} ); me._map.addOverlay(disObj.closeBtn); disObj.closeBtn.setTitle("清除本次测距"); // 点击关闭按钮,绑定关闭按钮事件 disObj.closeBtn.addEventListener("click", function(e){ // 关闭本次测距,清除相关存储和变量 for (var i = 0, l = disObj.points.length; i < l; i ++){ disObj.points[i].disLabel.remove(); disObj.points[i].disLabel = null; } for (var i = 0, l = disObj.paths.length; i < l; i ++){ disObj.paths[i].remove(); disObj.paths[i] = null; } for (var i = 0, l = disObj.dots.length; i < l; i ++){ disObj.dots[i].remove(); disObj.dots[i] = null; } disObj.closeBtn.remove(); disObj.closeBtn = null; stopBubble(e); /** * @ignore * 测距结束后,点击线段上最后一个节点旁的关闭按钮时,派发事件的接口 * @name DistanceTool#onremovepolyline * @event * @param {Event Object} e 回调函数会返回event参数 * * @example 参考示例:
* myDistanceToolObject.addEventListener("removepolyline", function(e) { alert(e.type); }); */ // 生成名为onremovepolyline的baidu.lang.Event对象 // 然后在此刻,将绑定在onremovepolyline上事件,全部赋予event参数,然后派发出去 var event = new baidu.lang.Event("onremovepolyline"); me.dispatchEvent(event); }); me._initData(); }; /** * 生成测距过程中的文字提示框 * @param {String} type * @param {String} text * @param {String} distance * @param {Label} label * @return 无返回值 */ DistanceTool.prototype._formatTitle = function(type, text, distance, label){ var title = label || this._followTitle; if (!title){ return; } title.setStyle({"lineHeight" : "16px", "zIndex" : "85", "padding" : "3px 5px"}); var t = this._startFollowText || ""; var htmls = []; if (type == 1){ // 测距过程中的提示 title.setOffset(0, 25); var u = this._opts.unit; var unit = this._units[u].u1; var dis = this._convertUnit(distance, u); if (dis > this._units[u].incon){ dis = dis / this._units[u].incon; unit = this._units[u].u2; dis = dis.toFixed(1); } else { dis = dis.toFixed(0); } htmls.push("总长:" + dis + "" + unit + "
"); htmls.push("" + text + ""); } else if (type == 2) { // 结束时的总距离展示 var u = this._opts.unit; var unit = this._units[u].u1; var dis = this._convertUnit(this._getTotalDistance(), u); if (dis > this._units[u].incon){ dis = dis / this._units[u].incon; unit = this._units[u].u2; dis = dis.toFixed(1); } else{ dis = dis.toFixed(0); } htmls.push("总长:" + dis + "" + unit); } else { title.setOffset(0, 25); htmls.push(t); } title.setContent(htmls.join("")); }; /** * 更新label的距离 * @param HTMLElement label的DOM元素 * @param Number 距离 */ DistanceTool.prototype._updateInstDis = function(label, dis){ // 换算距离 var u = this._opts.unit; var unit = this._units[u].u1; if (dis > this._units[u].incon){ dis = dis / this._units[u].incon; unit = this._units[u].u2; dis = dis.toFixed(1); } else { dis = dis.toFixed(0); } // 修改Label的内容 if (label) { var htmls = []; htmls.push("总长:" + dis + "" + unit + "
"); htmls.push("" + this._opts.followText + ""); label.setContent(htmls.join("")); } }; /** * 隐藏相关的线段和提示框文字 * @return 无返回值 */ DistanceTool.prototype._hideCurrent = function(){ if (!this._isOpen){ return; } if (this._paths.length > 0){ var p = this._paths[this._paths.length - 1]; p.hide(); } this._followTitle && this._followTitle.hide(); }; /** * 验证传入点的位置合理性 * @param {BMap.Point} pt 需要被验证的point点 * @return 无返回值 */ DistanceTool.prototype._isPointValid = function(pt){ if (!pt){ return false; } var mapBounds = this._map.getBounds(); var sw = mapBounds.getSouthWest(), ne = mapBounds.getNorthEast(); if (pt.lng < sw.lng || pt.lng > ne.lng || pt.lat < sw.lat || pt.lat > ne.lat) { return false; } return true; }; /** * 停止事件冒泡传播, * 闭包,对外不暴露 * * @type {Event} e e对象 */ function stopBubble(e){ var e = global.event || e; e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true; }; return DistanceTool; });