leaflet.label.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /*
  2. Leaflet.label, a plugin that adds labels to markers and vectors for Leaflet powered maps.
  3. (c) 2012-2013, Jacob Toye, Smartrak
  4. https://github.com/Leaflet/Leaflet.label
  5. http://leafletjs.com
  6. https://github.com/jacobtoye
  7. */
  8. (function () {
  9. //var L = window.L;
  10. /*
  11. * Leaflet.label assumes that you have already included the Leaflet library.
  12. */
  13. //L.labelVersion = '0.2.2-dev';
  14. L.Label = (L.Layer ? L.Layer : L.Class).extend({
  15. // includes: L.Mixin.Events,
  16. includes: L.Events,
  17. options: {
  18. className: "",
  19. clickable: false,
  20. direction: "auto",
  21. noHide: false,
  22. offset: [13, -16], // 6 (width of the label triangle) + 6 (padding)
  23. opacity: 1,
  24. zoomAnimation: true,
  25. },
  26. initialize: function (options, source) {
  27. L.setOptions(this, options);
  28. this._source = source;
  29. this._animated = L.Browser.any3d && this.options.zoomAnimation;
  30. this._isOpen = false;
  31. },
  32. onAdd: function (map) {
  33. this._map = map;
  34. this._pane = this.options.pane
  35. ? map._panes[this.options.pane]
  36. : this._source instanceof L.Marker
  37. ? map._panes.markerPane
  38. : map._panes.popupPane;
  39. if (!this._container) {
  40. this._initLayout();
  41. }
  42. this._pane.appendChild(this._container);
  43. this._initInteraction();
  44. this._update();
  45. this.setOpacity(this.options.opacity);
  46. map
  47. .on("moveend", this._onMoveEnd, this)
  48. .on("viewreset", this._onViewReset, this);
  49. if (this._animated) {
  50. map.on("zoomanim", this._zoomAnimation, this);
  51. }
  52. if (L.Browser.touch && !this.options.noHide) {
  53. L.DomEvent.on(this._container, "click", this.close, this);
  54. map.on("click", this.close, this);
  55. }
  56. },
  57. onRemove: function (map) {
  58. this._pane.removeChild(this._container);
  59. map.off(
  60. {
  61. zoomanim: this._zoomAnimation,
  62. moveend: this._onMoveEnd,
  63. viewreset: this._onViewReset,
  64. },
  65. this
  66. );
  67. this._removeInteraction();
  68. this._map = null;
  69. },
  70. setLatLng: function (latlng) {
  71. this._latlng = L.latLng(latlng);
  72. if (this._map) {
  73. this._updatePosition();
  74. }
  75. return this;
  76. },
  77. setContent: function (content) {
  78. // Backup previous content and store new content
  79. this._previousContent = this._content;
  80. this._content = content;
  81. this._updateContent();
  82. return this;
  83. },
  84. close: function () {
  85. var map = this._map;
  86. if (map) {
  87. if (L.Browser.touch && !this.options.noHide) {
  88. L.DomEvent.off(this._container, "click", this.close);
  89. map.off("click", this.close, this);
  90. }
  91. map.removeLayer(this);
  92. }
  93. },
  94. updateZIndex: function (zIndex) {
  95. this._zIndex = zIndex;
  96. if (this._container && this._zIndex) {
  97. this._container.style.zIndex = zIndex;
  98. }
  99. },
  100. setOpacity: function (opacity) {
  101. this.options.opacity = opacity;
  102. if (this._container) {
  103. L.DomUtil.setOpacity(this._container, opacity);
  104. }
  105. },
  106. _initLayout: function () {
  107. this._container = L.DomUtil.create(
  108. "div",
  109. "leaflet-label " + this.options.className + " leaflet-zoom-animated"
  110. );
  111. this.updateZIndex(this._zIndex);
  112. },
  113. _update: function () {
  114. if (!this._map) {
  115. return;
  116. }
  117. this._container.style.visibility = "hidden";
  118. this._updateContent();
  119. this._updatePosition();
  120. this._container.style.visibility = "";
  121. },
  122. _updateContent: function () {
  123. if (!this._content || !this._map || this._prevContent === this._content) {
  124. return;
  125. }
  126. if (typeof this._content === "string") {
  127. this._container.innerHTML = this._content;
  128. this._prevContent = this._content;
  129. this._labelWidth = this._container.offsetWidth;
  130. } else {
  131. this._container.innerHTML = "";
  132. this._container.appendChild(this._content);
  133. this._prevContent = this._content;
  134. this._labelWidth = this._container.offsetWidth;
  135. }
  136. //==S== 修改标记
  137. L.DomUtil.create("div", "leaflet-label-tips", this._container);
  138. //==E== 修改标记
  139. },
  140. _updatePosition: function () {
  141. var pos = this._map.latLngToLayerPoint(this._latlng);
  142. this._setPosition(pos);
  143. },
  144. _setPosition: function (pos) {
  145. var map = this._map,
  146. container = this._container,
  147. centerPoint = map.latLngToContainerPoint(map.getCenter()),
  148. labelPoint = map.layerPointToContainerPoint(pos),
  149. direction = this.options.direction,
  150. labelWidth = this._labelWidth,
  151. offset = L.point(this.options.offset);
  152. // position to the right (right or auto & needs to)
  153. if (
  154. direction === "right" ||
  155. (direction === "auto" && labelPoint.x < centerPoint.x)
  156. ) {
  157. L.DomUtil.addClass(container, "leaflet-label-right");
  158. L.DomUtil.removeClass(container, "leaflet-label-left");
  159. pos = pos.add(offset);
  160. } else {
  161. // position to the left
  162. L.DomUtil.addClass(container, "leaflet-label-left");
  163. L.DomUtil.removeClass(container, "leaflet-label-right");
  164. pos = pos.add(L.point(-offset.x - labelWidth, offset.y));
  165. }
  166. L.DomUtil.setPosition(container, pos);
  167. },
  168. _zoomAnimation: function (opt) {
  169. var pos = this._map
  170. ._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center)
  171. .round();
  172. this._setPosition(pos);
  173. },
  174. _onMoveEnd: function () {
  175. if (!this._animated || this.options.direction === "auto") {
  176. this._updatePosition();
  177. }
  178. },
  179. _onViewReset: function (e) {
  180. /* if map resets hard, we must update the label */
  181. if (e && e.hard) {
  182. this._update();
  183. }
  184. },
  185. _initInteraction: function () {
  186. if (!this.options.clickable) {
  187. return;
  188. }
  189. var container = this._container,
  190. events = [
  191. "dblclick",
  192. "mousedown",
  193. "mouseover",
  194. "mouseout",
  195. "contextmenu",
  196. ];
  197. L.DomUtil.addClass(container, "leaflet-clickable");
  198. L.DomEvent.on(container, "click", this._onMouseClick, this);
  199. for (var i = 0; i < events.length; i++) {
  200. L.DomEvent.on(container, events[i], this._fireMouseEvent, this);
  201. }
  202. },
  203. _removeInteraction: function () {
  204. if (!this.options.clickable) {
  205. return;
  206. }
  207. var container = this._container,
  208. events = [
  209. "dblclick",
  210. "mousedown",
  211. "mouseover",
  212. "mouseout",
  213. "contextmenu",
  214. ];
  215. L.DomUtil.removeClass(container, "leaflet-clickable");
  216. L.DomEvent.off(container, "click", this._onMouseClick, this);
  217. for (var i = 0; i < events.length; i++) {
  218. L.DomEvent.off(container, events[i], this._fireMouseEvent, this);
  219. }
  220. },
  221. _onMouseClick: function (e) {
  222. if (this.hasEventListeners(e.type)) {
  223. L.DomEvent.stopPropagation(e);
  224. }
  225. this.fire(e.type, {
  226. originalEvent: e,
  227. });
  228. },
  229. _fireMouseEvent: function (e) {
  230. this.fire(e.type, {
  231. originalEvent: e,
  232. });
  233. // TODO proper custom event propagation
  234. // this line will always be called if marker is in a FeatureGroup
  235. if (e.type === "contextmenu" && this.hasEventListeners(e.type)) {
  236. L.DomEvent.preventDefault(e);
  237. }
  238. if (e.type !== "mousedown") {
  239. L.DomEvent.stopPropagation(e);
  240. } else {
  241. L.DomEvent.preventDefault(e);
  242. }
  243. },
  244. });
  245. // This object is a mixin for L.Marker and L.CircleMarker. We declare it here as both need to include the contents.
  246. L.BaseMarkerMethods = {
  247. showLabel: function () {
  248. if (this.label && this._map) {
  249. this.label.setLatLng(this._latlng);
  250. this._map.showLabel(this.label);
  251. }
  252. return this;
  253. },
  254. hideLabel: function () {
  255. if (this.label) {
  256. this.label.close();
  257. }
  258. return this;
  259. },
  260. setLabelNoHide: function (noHide) {
  261. if (this._labelNoHide === noHide) {
  262. return;
  263. }
  264. this._labelNoHide = noHide;
  265. if (noHide) {
  266. this._removeLabelRevealHandlers();
  267. this.showLabel();
  268. } else {
  269. this._addLabelRevealHandlers();
  270. this.hideLabel();
  271. }
  272. },
  273. bindLabel: function (content, options) {
  274. if (this.label) {
  275. return false;
  276. }
  277. var labelAnchor = this.options.icon
  278. ? this.options.icon.options.labelAnchor
  279. : this.options.labelAnchor,
  280. anchor = L.point(labelAnchor) || L.point(0, 0);
  281. anchor = anchor.add(L.Label.prototype.options.offset);
  282. if (options && options.offset) {
  283. anchor = anchor.add(options.offset);
  284. }
  285. options = L.Util.extend({ offset: anchor }, options);
  286. this._labelNoHide = options.noHide;
  287. if (!this.label) {
  288. if (!this._labelNoHide) {
  289. this._addLabelRevealHandlers();
  290. }
  291. this.on("remove", this.hideLabel, this)
  292. .on("move", this._moveLabel, this)
  293. .on("add", this._onMarkerAdd, this);
  294. this._hasLabelHandlers = true;
  295. }
  296. this.label = new L.Label(options, this).setContent(content);
  297. return this;
  298. },
  299. unbindLabel: function () {
  300. if (this.label) {
  301. this.hideLabel();
  302. this.label = null;
  303. if (this._hasLabelHandlers) {
  304. if (!this._labelNoHide) {
  305. this._removeLabelRevealHandlers();
  306. }
  307. this.off("remove", this.hideLabel, this)
  308. .off("move", this._moveLabel, this)
  309. .off("add", this._onMarkerAdd, this);
  310. }
  311. this._hasLabelHandlers = false;
  312. }
  313. return this;
  314. },
  315. updateLabelContent: function (content) {
  316. if (this.label) {
  317. this.label.setContent(content);
  318. }
  319. },
  320. getLabel: function () {
  321. return this.label;
  322. },
  323. _onMarkerAdd: function () {
  324. if (this._labelNoHide) {
  325. this.showLabel();
  326. }
  327. },
  328. _addLabelRevealHandlers: function () {
  329. this.on("mouseover", this.showLabel, this).on(
  330. "mouseout",
  331. this.hideLabel,
  332. this
  333. );
  334. if (L.Browser.touch) {
  335. this.on("click", this.showLabel, this);
  336. }
  337. },
  338. _removeLabelRevealHandlers: function () {
  339. this.off("mouseover", this.showLabel, this).off(
  340. "mouseout",
  341. this.hideLabel,
  342. this
  343. );
  344. if (L.Browser.touch) {
  345. this.off("click", this.showLabel, this);
  346. }
  347. },
  348. _moveLabel: function (e) {
  349. this.label.setLatLng(e.latlng);
  350. },
  351. };
  352. // Add in an option to icon that is used to set where the label anchor is
  353. L.Icon.Default.mergeOptions({
  354. labelAnchor: new L.Point(4, -15),
  355. });
  356. // Have to do this since Leaflet is loaded before this plugin and initializes
  357. // L.Marker.options.icon therefore missing our mixin above.
  358. L.Marker.mergeOptions({
  359. icon: new L.Icon.Default(),
  360. });
  361. L.Marker.include(L.BaseMarkerMethods);
  362. L.Marker.include({
  363. _originalUpdateZIndex: L.Marker.prototype._updateZIndex,
  364. _updateZIndex: function (offset) {
  365. var zIndex = this._zIndex + offset;
  366. this._originalUpdateZIndex(offset);
  367. if (this.label) {
  368. this.label.updateZIndex(zIndex);
  369. }
  370. },
  371. _originalSetOpacity: L.Marker.prototype.setOpacity,
  372. setOpacity: function (opacity, labelHasSemiTransparency) {
  373. this.options.labelHasSemiTransparency = labelHasSemiTransparency;
  374. this._originalSetOpacity(opacity);
  375. },
  376. _originalUpdateOpacity: L.Marker.prototype._updateOpacity,
  377. _updateOpacity: function () {
  378. var absoluteOpacity = this.options.opacity === 0 ? 0 : 1;
  379. this._originalUpdateOpacity();
  380. if (this.label) {
  381. this.label.setOpacity(
  382. this.options.labelHasSemiTransparency
  383. ? this.options.opacity
  384. : absoluteOpacity
  385. );
  386. }
  387. },
  388. _originalSetLatLng: L.Marker.prototype.setLatLng,
  389. setLatLng: function (latlng) {
  390. if (this.label && !this._labelNoHide) {
  391. this.hideLabel();
  392. }
  393. return this._originalSetLatLng(latlng);
  394. },
  395. });
  396. // Add in an option to icon that is used to set where the label anchor is
  397. L.CircleMarker.mergeOptions({
  398. labelAnchor: new L.Point(-5, 5),
  399. });
  400. L.CircleMarker.include(L.BaseMarkerMethods);
  401. L.Path.include({
  402. bindLabel: function (content, options) {
  403. if (!this.label || this.label.options !== options) {
  404. this.label = new L.Label(options, this);
  405. }
  406. this.label.setContent(content);
  407. if (!this._showLabelAdded) {
  408. this.on("mouseover", this._showLabel, this)
  409. .on("mousemove", this._moveLabel, this)
  410. .on("mouseout remove", this._hideLabel, this);
  411. if (L.Browser.touch) {
  412. this.on("click", this._showLabel, this);
  413. }
  414. this._showLabelAdded = true;
  415. }
  416. return this;
  417. },
  418. unbindLabel: function () {
  419. if (this.label) {
  420. this._hideLabel();
  421. this.label = null;
  422. this._showLabelAdded = false;
  423. this.off("mouseover", this._showLabel, this)
  424. .off("mousemove", this._moveLabel, this)
  425. .off("mouseout remove", this._hideLabel, this);
  426. }
  427. return this;
  428. },
  429. updateLabelContent: function (content) {
  430. if (this.label) {
  431. this.label.setContent(content);
  432. }
  433. },
  434. _showLabel: function (e) {
  435. this.label.setLatLng(e.latlng);
  436. this._map.showLabel(this.label);
  437. },
  438. _moveLabel: function (e) {
  439. this.label.setLatLng(e.latlng);
  440. },
  441. _hideLabel: function () {
  442. this.label.close();
  443. },
  444. });
  445. L.Map.include({
  446. showLabel: function (label) {
  447. return this.addLayer(label);
  448. },
  449. });
  450. L.FeatureGroup.include({
  451. // TODO: remove this when AOP is supported in Leaflet, need this as we cannot put code in removeLayer()
  452. clearLayers: function () {
  453. this.unbindLabel();
  454. this.eachLayer(this.removeLayer, this);
  455. return this;
  456. },
  457. bindLabel: function (content, options) {
  458. return this.invoke("bindLabel", content, options);
  459. },
  460. unbindLabel: function () {
  461. return this.invoke("unbindLabel");
  462. },
  463. updateLabelContent: function (content) {
  464. this.invoke("updateLabelContent", content);
  465. },
  466. });
  467. })(window, document);