Source: Scene/LabelCollection.js

  1. /*global define*/
  2. define([
  3. '../Core/Cartesian2',
  4. '../Core/defaultValue',
  5. '../Core/defined',
  6. '../Core/defineProperties',
  7. '../Core/destroyObject',
  8. '../Core/DeveloperError',
  9. '../Core/Matrix4',
  10. '../Core/writeTextToCanvas',
  11. './BillboardCollection',
  12. './HeightReference',
  13. './HorizontalOrigin',
  14. './Label',
  15. './LabelStyle',
  16. './TextureAtlas',
  17. './VerticalOrigin'
  18. ], function(
  19. Cartesian2,
  20. defaultValue,
  21. defined,
  22. defineProperties,
  23. destroyObject,
  24. DeveloperError,
  25. Matrix4,
  26. writeTextToCanvas,
  27. BillboardCollection,
  28. HeightReference,
  29. HorizontalOrigin,
  30. Label,
  31. LabelStyle,
  32. TextureAtlas,
  33. VerticalOrigin) {
  34. 'use strict';
  35. // A glyph represents a single character in a particular label. It may or may
  36. // not have a billboard, depending on whether the texture info has an index into
  37. // the the label collection's texture atlas. Invisible characters have no texture, and
  38. // no billboard. However, it always has a valid dimensions object.
  39. function Glyph() {
  40. this.textureInfo = undefined;
  41. this.dimensions = undefined;
  42. this.billboard = undefined;
  43. }
  44. // GlyphTextureInfo represents a single character, drawn in a particular style,
  45. // shared and reference counted across all labels. It may or may not have an
  46. // index into the label collection's texture atlas, depending on whether the character
  47. // has both width and height, but it always has a valid dimensions object.
  48. function GlyphTextureInfo(labelCollection, index, dimensions) {
  49. this.labelCollection = labelCollection;
  50. this.index = index;
  51. this.dimensions = dimensions;
  52. }
  53. // reusable object for calling writeTextToCanvas
  54. var writeTextToCanvasParameters = {};
  55. function createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin) {
  56. writeTextToCanvasParameters.font = font;
  57. writeTextToCanvasParameters.fillColor = fillColor;
  58. writeTextToCanvasParameters.strokeColor = outlineColor;
  59. writeTextToCanvasParameters.strokeWidth = outlineWidth;
  60. if (verticalOrigin === VerticalOrigin.BOTTOM) {
  61. writeTextToCanvasParameters.textBaseline = 'bottom';
  62. } else if (verticalOrigin === VerticalOrigin.TOP) {
  63. writeTextToCanvasParameters.textBaseline = 'top';
  64. } else {
  65. // VerticalOrigin.CENTER
  66. writeTextToCanvasParameters.textBaseline = 'middle';
  67. }
  68. writeTextToCanvasParameters.fill = style === LabelStyle.FILL || style === LabelStyle.FILL_AND_OUTLINE;
  69. writeTextToCanvasParameters.stroke = style === LabelStyle.OUTLINE || style === LabelStyle.FILL_AND_OUTLINE;
  70. return writeTextToCanvas(character, writeTextToCanvasParameters);
  71. }
  72. function unbindGlyph(labelCollection, glyph) {
  73. glyph.textureInfo = undefined;
  74. glyph.dimensions = undefined;
  75. var billboard = glyph.billboard;
  76. if (defined(billboard)) {
  77. billboard.show = false;
  78. billboard.image = undefined;
  79. labelCollection._spareBillboards.push(billboard);
  80. glyph.billboard = undefined;
  81. }
  82. }
  83. function addGlyphToTextureAtlas(textureAtlas, id, canvas, glyphTextureInfo) {
  84. textureAtlas.addImage(id, canvas).then(function(index, id) {
  85. glyphTextureInfo.index = index;
  86. });
  87. }
  88. function rebindAllGlyphs(labelCollection, label) {
  89. var text = label._text;
  90. var textLength = text.length;
  91. var glyphs = label._glyphs;
  92. var glyphsLength = glyphs.length;
  93. var glyph;
  94. var glyphIndex;
  95. var textIndex;
  96. // if we have more glyphs than needed, unbind the extras.
  97. if (textLength < glyphsLength) {
  98. for (glyphIndex = textLength; glyphIndex < glyphsLength; ++glyphIndex) {
  99. unbindGlyph(labelCollection, glyphs[glyphIndex]);
  100. }
  101. }
  102. // presize glyphs to match the new text length
  103. glyphs.length = textLength;
  104. var glyphTextureCache = labelCollection._glyphTextureCache;
  105. // walk the text looking for new characters (creating new glyphs for each)
  106. // or changed characters (rebinding existing glyphs)
  107. for (textIndex = 0; textIndex < textLength; ++textIndex) {
  108. var character = text.charAt(textIndex);
  109. var font = label._font;
  110. var fillColor = label._fillColor;
  111. var outlineColor = label._outlineColor;
  112. var outlineWidth = label._outlineWidth;
  113. var style = label._style;
  114. var verticalOrigin = label._verticalOrigin;
  115. // retrieve glyph dimensions and texture index (if the canvas has area)
  116. // from the glyph texture cache, or create and add if not present.
  117. var id = JSON.stringify([
  118. character,
  119. font,
  120. fillColor.toRgba(),
  121. outlineColor.toRgba(),
  122. outlineWidth,
  123. +style,
  124. +verticalOrigin
  125. ]);
  126. var glyphTextureInfo = glyphTextureCache[id];
  127. if (!defined(glyphTextureInfo)) {
  128. var canvas = createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin);
  129. glyphTextureInfo = new GlyphTextureInfo(labelCollection, -1, canvas.dimensions);
  130. glyphTextureCache[id] = glyphTextureInfo;
  131. if (canvas.width > 0 && canvas.height > 0) {
  132. addGlyphToTextureAtlas(labelCollection._textureAtlas, id, canvas, glyphTextureInfo);
  133. }
  134. }
  135. glyph = glyphs[textIndex];
  136. if (defined(glyph)) {
  137. // clean up leftover information from the previous glyph
  138. if (glyphTextureInfo.index === -1) {
  139. // no texture, and therefore no billboard, for this glyph.
  140. // so, completely unbind glyph.
  141. unbindGlyph(labelCollection, glyph);
  142. } else {
  143. // we have a texture and billboard. If we had one before, release
  144. // our reference to that texture info, but reuse the billboard.
  145. if (defined(glyph.textureInfo)) {
  146. glyph.textureInfo = undefined;
  147. }
  148. }
  149. } else {
  150. // create a glyph object
  151. glyph = new Glyph();
  152. glyphs[textIndex] = glyph;
  153. }
  154. glyph.textureInfo = glyphTextureInfo;
  155. glyph.dimensions = glyphTextureInfo.dimensions;
  156. // if we have a texture, configure the existing billboard, or obtain one
  157. if (glyphTextureInfo.index !== -1) {
  158. var billboard = glyph.billboard;
  159. if (!defined(billboard)) {
  160. if (labelCollection._spareBillboards.length > 0) {
  161. billboard = labelCollection._spareBillboards.pop();
  162. } else {
  163. billboard = labelCollection._billboardCollection.add({
  164. collection : labelCollection
  165. });
  166. }
  167. glyph.billboard = billboard;
  168. }
  169. billboard.show = label._show;
  170. billboard.position = label._position;
  171. billboard.eyeOffset = label._eyeOffset;
  172. billboard.pixelOffset = label._pixelOffset;
  173. billboard.horizontalOrigin = HorizontalOrigin.LEFT;
  174. billboard.verticalOrigin = label._verticalOrigin;
  175. billboard.heightReference = label._heightReference;
  176. billboard.scale = label._scale;
  177. billboard.pickPrimitive = label;
  178. billboard.id = label._id;
  179. billboard.image = id;
  180. billboard.translucencyByDistance = label._translucencyByDistance;
  181. billboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance;
  182. billboard.distanceDisplayCondition = label._distanceDisplayCondition;
  183. }
  184. }
  185. // changing glyphs will cause the position of the
  186. // glyphs to change, since different characters have different widths
  187. label._repositionAllGlyphs = true;
  188. }
  189. // reusable Cartesian2 instance
  190. var glyphPixelOffset = new Cartesian2();
  191. function repositionAllGlyphs(label, resolutionScale) {
  192. var glyphs = label._glyphs;
  193. var glyph;
  194. var dimensions;
  195. var totalWidth = 0;
  196. var maxHeight = 0;
  197. var glyphIndex = 0;
  198. var glyphLength = glyphs.length;
  199. for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
  200. glyph = glyphs[glyphIndex];
  201. dimensions = glyph.dimensions;
  202. totalWidth += dimensions.computedWidth;
  203. maxHeight = Math.max(maxHeight, dimensions.height);
  204. }
  205. var scale = label._scale;
  206. var horizontalOrigin = label._horizontalOrigin;
  207. var widthOffset = 0;
  208. if (horizontalOrigin === HorizontalOrigin.CENTER) {
  209. widthOffset -= totalWidth / 2 * scale;
  210. } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
  211. widthOffset -= totalWidth * scale;
  212. }
  213. glyphPixelOffset.x = widthOffset * resolutionScale;
  214. glyphPixelOffset.y = 0;
  215. var verticalOrigin = label._verticalOrigin;
  216. for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
  217. glyph = glyphs[glyphIndex];
  218. dimensions = glyph.dimensions;
  219. if (verticalOrigin === VerticalOrigin.BOTTOM || dimensions.height === maxHeight) {
  220. glyphPixelOffset.y = -dimensions.descent * scale;
  221. } else if (verticalOrigin === VerticalOrigin.TOP) {
  222. glyphPixelOffset.y = -(maxHeight - dimensions.height) * scale - dimensions.descent * scale;
  223. } else if (verticalOrigin === VerticalOrigin.CENTER) {
  224. glyphPixelOffset.y = -(maxHeight - dimensions.height) / 2 * scale - dimensions.descent * scale;
  225. }
  226. glyphPixelOffset.y *= resolutionScale;
  227. if (defined(glyph.billboard)) {
  228. glyph.billboard._setTranslate(glyphPixelOffset);
  229. }
  230. glyphPixelOffset.x += dimensions.computedWidth * scale * resolutionScale;
  231. }
  232. }
  233. function destroyLabel(labelCollection, label) {
  234. var glyphs = label._glyphs;
  235. for ( var i = 0, len = glyphs.length; i < len; ++i) {
  236. unbindGlyph(labelCollection, glyphs[i]);
  237. }
  238. label._labelCollection = undefined;
  239. if (defined(label._removeCallbackFunc)) {
  240. label._removeCallbackFunc();
  241. }
  242. destroyObject(label);
  243. }
  244. /**
  245. * A renderable collection of labels. Labels are viewport-aligned text positioned in the 3D scene.
  246. * Each label can have a different font, color, scale, etc.
  247. * <br /><br />
  248. * <div align='center'>
  249. * <img src='images/Label.png' width='400' height='300' /><br />
  250. * Example labels
  251. * </div>
  252. * <br /><br />
  253. * Labels are added and removed from the collection using {@link LabelCollection#add}
  254. * and {@link LabelCollection#remove}.
  255. *
  256. * @alias LabelCollection
  257. * @constructor
  258. *
  259. * @param {Object} [options] Object with the following properties:
  260. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each label from model to world coordinates.
  261. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
  262. * @param {Scene} [options.scene] Must be passed in for labels that use the height reference property or will be depth tested against the globe.
  263. *
  264. * @performance For best performance, prefer a few collections, each with many labels, to
  265. * many collections with only a few labels each. Avoid having collections where some
  266. * labels change every frame and others do not; instead, create one or more collections
  267. * for static labels, and one or more collections for dynamic labels.
  268. *
  269. * @see LabelCollection#add
  270. * @see LabelCollection#remove
  271. * @see Label
  272. * @see BillboardCollection
  273. *
  274. * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
  275. *
  276. * @example
  277. * // Create a label collection with two labels
  278. * var labels = scene.primitives.add(new Cesium.LabelCollection());
  279. * labels.add({
  280. * position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
  281. * text : 'A label'
  282. * });
  283. * labels.add({
  284. * position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
  285. * text : 'Another label'
  286. * });
  287. */
  288. function LabelCollection(options) {
  289. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  290. this._scene = options.scene;
  291. this._textureAtlas = undefined;
  292. this._billboardCollection = new BillboardCollection({
  293. scene : this._scene
  294. });
  295. this._billboardCollection.destroyTextureAtlas = false;
  296. this._spareBillboards = [];
  297. this._glyphTextureCache = {};
  298. this._labels = [];
  299. this._labelsToUpdate = [];
  300. this._totalGlyphCount = 0;
  301. this._resolutionScale = undefined;
  302. /**
  303. * The 4x4 transformation matrix that transforms each label in this collection from model to world coordinates.
  304. * When this is the identity matrix, the labels are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
  305. * Local reference frames can be used by providing a different transformation matrix, like that returned
  306. * by {@link Transforms.eastNorthUpToFixedFrame}.
  307. *
  308. * @type Matrix4
  309. * @default {@link Matrix4.IDENTITY}
  310. *
  311. * @example
  312. * var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
  313. * labels.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
  314. * labels.add({
  315. * position : new Cesium.Cartesian3(0.0, 0.0, 0.0),
  316. * text : 'Center'
  317. * });
  318. * labels.add({
  319. * position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0),
  320. * text : 'East'
  321. * });
  322. * labels.add({
  323. * position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0),
  324. * text : 'North'
  325. * });
  326. * labels.add({
  327. * position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0),
  328. * text : 'Up'
  329. * });
  330. */
  331. this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
  332. /**
  333. * This property is for debugging only; it is not for production use nor is it optimized.
  334. * <p>
  335. * Draws the bounding sphere for each draw command in the primitive.
  336. * </p>
  337. *
  338. * @type {Boolean}
  339. *
  340. * @default false
  341. */
  342. this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
  343. }
  344. defineProperties(LabelCollection.prototype, {
  345. /**
  346. * Returns the number of labels in this collection. This is commonly used with
  347. * {@link LabelCollection#get} to iterate over all the labels
  348. * in the collection.
  349. * @memberof LabelCollection.prototype
  350. * @type {Number}
  351. */
  352. length : {
  353. get : function() {
  354. return this._labels.length;
  355. }
  356. }
  357. });
  358. /**
  359. * Creates and adds a label with the specified initial properties to the collection.
  360. * The added label is returned so it can be modified or removed from the collection later.
  361. *
  362. * @param {Object}[options] A template describing the label's properties as shown in Example 1.
  363. * @returns {Label} The label that was added to the collection.
  364. *
  365. * @performance Calling <code>add</code> is expected constant time. However, the collection's vertex buffer
  366. * is rewritten; this operations is <code>O(n)</code> and also incurs
  367. * CPU to GPU overhead. For best performance, add as many billboards as possible before
  368. * calling <code>update</code>.
  369. *
  370. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  371. *
  372. *
  373. * @example
  374. * // Example 1: Add a label, specifying all the default values.
  375. * var l = labels.add({
  376. * show : true,
  377. * position : Cesium.Cartesian3.ZERO,
  378. * text : '',
  379. * font : '30px sans-serif',
  380. * fillColor : Cesium.Color.WHITE,
  381. * outlineColor : Cesium.Color.BLACK,
  382. * style : Cesium.LabelStyle.FILL,
  383. * pixelOffset : Cesium.Cartesian2.ZERO,
  384. * eyeOffset : Cesium.Cartesian3.ZERO,
  385. * horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
  386. * verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
  387. * scale : 1.0
  388. * });
  389. *
  390. * @example
  391. * // Example 2: Specify only the label's cartographic position,
  392. * // text, and font.
  393. * var l = labels.add({
  394. * position : Cesium.Cartesian3.fromRadians(longitude, latitude, height),
  395. * text : 'Hello World',
  396. * font : '24px Helvetica',
  397. * });
  398. *
  399. * @see LabelCollection#remove
  400. * @see LabelCollection#removeAll
  401. */
  402. LabelCollection.prototype.add = function(options) {
  403. var label = new Label(options, this);
  404. this._labels.push(label);
  405. this._labelsToUpdate.push(label);
  406. return label;
  407. };
  408. /**
  409. * Removes a label from the collection. Once removed, a label is no longer usable.
  410. *
  411. * @param {Label} label The label to remove.
  412. * @returns {Boolean} <code>true</code> if the label was removed; <code>false</code> if the label was not found in the collection.
  413. *
  414. * @performance Calling <code>remove</code> is expected constant time. However, the collection's vertex buffer
  415. * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead. For
  416. * best performance, remove as many labels as possible before calling <code>update</code>.
  417. * If you intend to temporarily hide a label, it is usually more efficient to call
  418. * {@link Label#show} instead of removing and re-adding the label.
  419. *
  420. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  421. *
  422. *
  423. * @example
  424. * var l = labels.add(...);
  425. * labels.remove(l); // Returns true
  426. *
  427. * @see LabelCollection#add
  428. * @see LabelCollection#removeAll
  429. * @see Label#show
  430. */
  431. LabelCollection.prototype.remove = function(label) {
  432. if (defined(label) && label._labelCollection === this) {
  433. var index = this._labels.indexOf(label);
  434. if (index !== -1) {
  435. this._labels.splice(index, 1);
  436. destroyLabel(this, label);
  437. return true;
  438. }
  439. }
  440. return false;
  441. };
  442. /**
  443. * Removes all labels from the collection.
  444. *
  445. * @performance <code>O(n)</code>. It is more efficient to remove all the labels
  446. * from a collection and then add new ones than to create a new collection entirely.
  447. *
  448. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  449. *
  450. *
  451. * @example
  452. * labels.add(...);
  453. * labels.add(...);
  454. * labels.removeAll();
  455. *
  456. * @see LabelCollection#add
  457. * @see LabelCollection#remove
  458. */
  459. LabelCollection.prototype.removeAll = function() {
  460. var labels = this._labels;
  461. for ( var i = 0, len = labels.length; i < len; ++i) {
  462. destroyLabel(this, labels[i]);
  463. }
  464. labels.length = 0;
  465. };
  466. /**
  467. * Check whether this collection contains a given label.
  468. *
  469. * @param {Label} label The label to check for.
  470. * @returns {Boolean} true if this collection contains the label, false otherwise.
  471. *
  472. * @see LabelCollection#get
  473. */
  474. LabelCollection.prototype.contains = function(label) {
  475. return defined(label) && label._labelCollection === this;
  476. };
  477. /**
  478. * Returns the label in the collection at the specified index. Indices are zero-based
  479. * and increase as labels are added. Removing a label shifts all labels after
  480. * it to the left, changing their indices. This function is commonly used with
  481. * {@link LabelCollection#length} to iterate over all the labels
  482. * in the collection.
  483. *
  484. * @param {Number} index The zero-based index of the billboard.
  485. *
  486. * @returns {Label} The label at the specified index.
  487. *
  488. * @performance Expected constant time. If labels were removed from the collection and
  489. * {@link Scene#render} was not called, an implicit <code>O(n)</code>
  490. * operation is performed.
  491. *
  492. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  493. *
  494. *
  495. * @example
  496. * // Toggle the show property of every label in the collection
  497. * var len = labels.length;
  498. * for (var i = 0; i < len; ++i) {
  499. * var l = billboards.get(i);
  500. * l.show = !l.show;
  501. * }
  502. *
  503. * @see LabelCollection#length
  504. */
  505. LabelCollection.prototype.get = function(index) {
  506. //>>includeStart('debug', pragmas.debug);
  507. if (!defined(index)) {
  508. throw new DeveloperError('index is required.');
  509. }
  510. //>>includeEnd('debug');
  511. return this._labels[index];
  512. };
  513. /**
  514. * @private
  515. */
  516. LabelCollection.prototype.update = function(frameState) {
  517. var billboardCollection = this._billboardCollection;
  518. billboardCollection.modelMatrix = this.modelMatrix;
  519. billboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;
  520. var context = frameState.context;
  521. if (!defined(this._textureAtlas)) {
  522. this._textureAtlas = new TextureAtlas({
  523. context : context
  524. });
  525. billboardCollection.textureAtlas = this._textureAtlas;
  526. }
  527. var uniformState = context.uniformState;
  528. var resolutionScale = uniformState.resolutionScale;
  529. var resolutionChanged = this._resolutionScale !== resolutionScale;
  530. this._resolutionScale = resolutionScale;
  531. var labelsToUpdate;
  532. if (resolutionChanged) {
  533. labelsToUpdate = this._labels;
  534. } else {
  535. labelsToUpdate = this._labelsToUpdate;
  536. }
  537. var len = labelsToUpdate.length;
  538. for (var i = 0; i < len; ++i) {
  539. var label = labelsToUpdate[i];
  540. if (label.isDestroyed()) {
  541. continue;
  542. }
  543. var preUpdateGlyphCount = label._glyphs.length;
  544. if (label._rebindAllGlyphs) {
  545. rebindAllGlyphs(this, label);
  546. label._rebindAllGlyphs = false;
  547. }
  548. if (resolutionChanged || label._repositionAllGlyphs) {
  549. repositionAllGlyphs(label, resolutionScale);
  550. label._repositionAllGlyphs = false;
  551. }
  552. var glyphCountDifference = label._glyphs.length - preUpdateGlyphCount;
  553. this._totalGlyphCount += glyphCountDifference;
  554. }
  555. this._labelsToUpdate.length = 0;
  556. billboardCollection.update(frameState);
  557. };
  558. /**
  559. * Returns true if this object was destroyed; otherwise, false.
  560. * <br /><br />
  561. * If this object was destroyed, it should not be used; calling any function other than
  562. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  563. *
  564. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  565. *
  566. * @see LabelCollection#destroy
  567. */
  568. LabelCollection.prototype.isDestroyed = function() {
  569. return false;
  570. };
  571. /**
  572. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  573. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  574. * <br /><br />
  575. * Once an object is destroyed, it should not be used; calling any function other than
  576. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  577. * assign the return value (<code>undefined</code>) to the object as done in the example.
  578. *
  579. * @returns {undefined}
  580. *
  581. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  582. *
  583. *
  584. * @example
  585. * labels = labels && labels.destroy();
  586. *
  587. * @see LabelCollection#isDestroyed
  588. */
  589. LabelCollection.prototype.destroy = function() {
  590. this.removeAll();
  591. this._billboardCollection = this._billboardCollection.destroy();
  592. this._textureAtlas = this._textureAtlas && this._textureAtlas.destroy();
  593. return destroyObject(this);
  594. };
  595. return LabelCollection;
  596. });