Source: DataSources/PointVisualizer.js

  1. /*global define*/
  2. define([
  3. '../Core/AssociativeArray',
  4. '../Core/Cartesian3',
  5. '../Core/Color',
  6. '../Core/defaultValue',
  7. '../Core/defined',
  8. '../Core/destroyObject',
  9. '../Core/DeveloperError',
  10. '../Core/DistanceDisplayCondition',
  11. '../Core/NearFarScalar',
  12. '../Scene/HeightReference',
  13. './BoundingSphereState',
  14. './EntityCluster',
  15. './Property'
  16. ], function(
  17. AssociativeArray,
  18. Cartesian3,
  19. Color,
  20. defaultValue,
  21. defined,
  22. destroyObject,
  23. DeveloperError,
  24. DistanceDisplayCondition,
  25. NearFarScalar,
  26. HeightReference,
  27. BoundingSphereState,
  28. EntityCluster,
  29. Property) {
  30. 'use strict';
  31. var defaultColor = Color.WHITE;
  32. var defaultOutlineColor = Color.BLACK;
  33. var defaultOutlineWidth = 0.0;
  34. var defaultPixelSize = 1.0;
  35. var color = new Color();
  36. var position = new Cartesian3();
  37. var outlineColor = new Color();
  38. var scaleByDistance = new NearFarScalar();
  39. var translucencyByDistance = new NearFarScalar();
  40. var distanceDisplayCondition = new DistanceDisplayCondition();
  41. function EntityData(entity) {
  42. this.entity = entity;
  43. this.pointPrimitive = undefined;
  44. this.billboard = undefined;
  45. this.color = undefined;
  46. this.outlineColor = undefined;
  47. this.pixelSize = undefined;
  48. this.outlineWidth = undefined;
  49. }
  50. /**
  51. * A {@link Visualizer} which maps {@link Entity#point} to a {@link PointPrimitive}.
  52. * @alias PointVisualizer
  53. * @constructor
  54. *
  55. * @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities.
  56. * @param {EntityCollection} entityCollection The entityCollection to visualize.
  57. */
  58. function PointVisualizer(entityCluster, entityCollection) {
  59. //>>includeStart('debug', pragmas.debug);
  60. if (!defined(entityCluster)) {
  61. throw new DeveloperError('entityCluster is required.');
  62. }
  63. if (!defined(entityCollection)) {
  64. throw new DeveloperError('entityCollection is required.');
  65. }
  66. //>>includeEnd('debug');
  67. entityCollection.collectionChanged.addEventListener(PointVisualizer.prototype._onCollectionChanged, this);
  68. this._cluster = entityCluster;
  69. this._entityCollection = entityCollection;
  70. this._items = new AssociativeArray();
  71. this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
  72. }
  73. /**
  74. * Updates the primitives created by this visualizer to match their
  75. * Entity counterpart at the given time.
  76. *
  77. * @param {JulianDate} time The time to update to.
  78. * @returns {Boolean} This function always returns true.
  79. */
  80. PointVisualizer.prototype.update = function(time) {
  81. //>>includeStart('debug', pragmas.debug);
  82. if (!defined(time)) {
  83. throw new DeveloperError('time is required.');
  84. }
  85. //>>includeEnd('debug');
  86. var items = this._items.values;
  87. var cluster = this._cluster;
  88. for (var i = 0, len = items.length; i < len; i++) {
  89. var item = items[i];
  90. var entity = item.entity;
  91. var pointGraphics = entity._point;
  92. var pointPrimitive = item.pointPrimitive;
  93. var billboard = item.billboard;
  94. var heightReference = Property.getValueOrDefault(pointGraphics._heightReference, time, HeightReference.NONE);
  95. var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(pointGraphics._show, time, true);
  96. if (show) {
  97. position = Property.getValueOrUndefined(entity._position, time, position);
  98. show = defined(position);
  99. }
  100. if (!show) {
  101. returnPrimitive(item, entity, cluster);
  102. continue;
  103. }
  104. if (!Property.isConstant(entity._position)) {
  105. cluster._clusterDirty = true;
  106. }
  107. var needsRedraw = false;
  108. if ((heightReference !== HeightReference.NONE) && !defined(billboard)) {
  109. if (defined(pointPrimitive)) {
  110. returnPrimitive(item, entity, cluster);
  111. pointPrimitive = undefined;
  112. }
  113. billboard = cluster.getBillboard(entity);
  114. billboard.id = entity;
  115. billboard.image = undefined;
  116. item.billboard = billboard;
  117. needsRedraw = true;
  118. } else if ((heightReference === HeightReference.NONE) && !defined(pointPrimitive)) {
  119. if (defined(billboard)) {
  120. returnPrimitive(item, entity, cluster);
  121. billboard = undefined;
  122. }
  123. pointPrimitive = cluster.getPoint(entity);
  124. pointPrimitive.id = entity;
  125. item.pointPrimitive = pointPrimitive;
  126. }
  127. if (defined(pointPrimitive)) {
  128. pointPrimitive.show = true;
  129. pointPrimitive.position = position;
  130. pointPrimitive.scaleByDistance = Property.getValueOrUndefined(pointGraphics._scaleByDistance, time, scaleByDistance);
  131. pointPrimitive.translucencyByDistance = Property.getValueOrUndefined(pointGraphics._translucencyByDistance, time, translucencyByDistance);
  132. pointPrimitive.color = Property.getValueOrDefault(pointGraphics._color, time, defaultColor, color);
  133. pointPrimitive.outlineColor = Property.getValueOrDefault(pointGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
  134. pointPrimitive.outlineWidth = Property.getValueOrDefault(pointGraphics._outlineWidth, time, defaultOutlineWidth);
  135. pointPrimitive.pixelSize = Property.getValueOrDefault(pointGraphics._pixelSize, time, defaultPixelSize);
  136. pointPrimitive.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
  137. } else { // billboard
  138. billboard.show = true;
  139. billboard.position = position;
  140. billboard.scaleByDistance = Property.getValueOrUndefined(pointGraphics._scaleByDistance, time, scaleByDistance);
  141. billboard.translucencyByDistance = Property.getValueOrUndefined(pointGraphics._translucencyByDistance, time, translucencyByDistance);
  142. billboard.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
  143. billboard.heightReference = heightReference;
  144. var newColor = Property.getValueOrDefault(pointGraphics._color, time, defaultColor, color);
  145. var newOutlineColor = Property.getValueOrDefault(pointGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
  146. var newOutlineWidth = Math.round(Property.getValueOrDefault(pointGraphics._outlineWidth, time, defaultOutlineWidth));
  147. var newPixelSize = Math.max(1, Math.round(Property.getValueOrDefault(pointGraphics._pixelSize, time, defaultPixelSize)));
  148. if (newOutlineWidth > 0) {
  149. billboard.scale = 1.0;
  150. needsRedraw = needsRedraw || //
  151. newOutlineWidth !== item.outlineWidth || //
  152. newPixelSize !== item.pixelSize || //
  153. !Color.equals(newColor, item.color) || //
  154. !Color.equals(newOutlineColor, item.outlineColor);
  155. } else {
  156. billboard.scale = newPixelSize / 50.0;
  157. newPixelSize = 50.0;
  158. needsRedraw = needsRedraw || //
  159. newOutlineWidth !== item.outlineWidth || //
  160. !Color.equals(newColor, item.color) || //
  161. !Color.equals(newOutlineColor, item.outlineColor);
  162. }
  163. if (needsRedraw) {
  164. item.color = Color.clone(newColor, item.color);
  165. item.outlineColor = Color.clone(newOutlineColor, item.outlineColor);
  166. item.pixelSize = newPixelSize;
  167. item.outlineWidth = newOutlineWidth;
  168. var centerAlpha = newColor.alpha;
  169. var cssColor = newColor.toCssColorString();
  170. var cssOutlineColor = newOutlineColor.toCssColorString();
  171. var textureId = JSON.stringify([cssColor, newPixelSize, cssOutlineColor, newOutlineWidth]);
  172. billboard.setImage(textureId, createCallback(centerAlpha, cssColor, cssOutlineColor, newOutlineWidth, newPixelSize));
  173. }
  174. }
  175. }
  176. return true;
  177. };
  178. /**
  179. * Computes a bounding sphere which encloses the visualization produced for the specified entity.
  180. * The bounding sphere is in the fixed frame of the scene's globe.
  181. *
  182. * @param {Entity} entity The entity whose bounding sphere to compute.
  183. * @param {BoundingSphere} result The bounding sphere onto which to store the result.
  184. * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
  185. * BoundingSphereState.PENDING if the result is still being computed, or
  186. * BoundingSphereState.FAILED if the entity has no visualization in the current scene.
  187. * @private
  188. */
  189. PointVisualizer.prototype.getBoundingSphere = function(entity, result) {
  190. //>>includeStart('debug', pragmas.debug);
  191. if (!defined(entity)) {
  192. throw new DeveloperError('entity is required.');
  193. }
  194. if (!defined(result)) {
  195. throw new DeveloperError('result is required.');
  196. }
  197. //>>includeEnd('debug');
  198. var item = this._items.get(entity.id);
  199. if (!defined(item) || !(defined(item.pointPrimitive) || defined(item.billboard))) {
  200. return BoundingSphereState.FAILED;
  201. }
  202. if (defined(item.pointPrimitive)) {
  203. result.center = Cartesian3.clone(item.pointPrimitive.position, result.center);
  204. } else {
  205. var billboard = item.billboard;
  206. if (!defined(billboard._clampedPosition)) {
  207. return BoundingSphereState.PENDING;
  208. }
  209. result.center = Cartesian3.clone(billboard._clampedPosition, result.center);
  210. }
  211. result.radius = 0;
  212. return BoundingSphereState.DONE;
  213. };
  214. /**
  215. * Returns true if this object was destroyed; otherwise, false.
  216. *
  217. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  218. */
  219. PointVisualizer.prototype.isDestroyed = function() {
  220. return false;
  221. };
  222. /**
  223. * Removes and destroys all primitives created by this instance.
  224. */
  225. PointVisualizer.prototype.destroy = function() {
  226. this._entityCollection.collectionChanged.removeEventListener(PointVisualizer.prototype._onCollectionChanged, this);
  227. var entities = this._entityCollection.values;
  228. for (var i = 0; i < entities.length; i++) {
  229. this._cluster.removePoint(entities[i]);
  230. }
  231. return destroyObject(this);
  232. };
  233. PointVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
  234. var i;
  235. var entity;
  236. var items = this._items;
  237. var cluster = this._cluster;
  238. for (i = added.length - 1; i > -1; i--) {
  239. entity = added[i];
  240. if (defined(entity._point) && defined(entity._position)) {
  241. items.set(entity.id, new EntityData(entity));
  242. }
  243. }
  244. for (i = changed.length - 1; i > -1; i--) {
  245. entity = changed[i];
  246. if (defined(entity._point) && defined(entity._position)) {
  247. if (!items.contains(entity.id)) {
  248. items.set(entity.id, new EntityData(entity));
  249. }
  250. } else {
  251. returnPrimitive(items.get(entity.id), entity, cluster);
  252. items.remove(entity.id);
  253. }
  254. }
  255. for (i = removed.length - 1; i > -1; i--) {
  256. entity = removed[i];
  257. returnPrimitive(items.get(entity.id), entity, cluster);
  258. items.remove(entity.id);
  259. }
  260. };
  261. function returnPrimitive(item, entity, cluster) {
  262. if (defined(item)) {
  263. var pointPrimitive = item.pointPrimitive;
  264. if (defined(pointPrimitive)) {
  265. item.pointPrimitive = undefined;
  266. cluster.removePoint(entity);
  267. return;
  268. }
  269. var billboard = item.billboard;
  270. if (defined(billboard)) {
  271. item.billboard = undefined;
  272. cluster.removeBillboard(entity);
  273. }
  274. }
  275. }
  276. function createCallback(centerAlpha, cssColor, cssOutlineColor, cssOutlineWidth, newPixelSize) {
  277. return function(id) {
  278. var canvas = document.createElement('canvas');
  279. var length = newPixelSize + (2 * cssOutlineWidth);
  280. canvas.height = canvas.width = length;
  281. var context2D = canvas.getContext('2d');
  282. context2D.clearRect(0, 0, length, length);
  283. if (cssOutlineWidth !== 0) {
  284. context2D.beginPath();
  285. context2D.arc(length / 2, length / 2, length / 2, 0, 2 * Math.PI, true);
  286. context2D.closePath();
  287. context2D.fillStyle = cssOutlineColor;
  288. context2D.fill();
  289. // Punch a hole in the center if needed.
  290. if (centerAlpha < 1.0) {
  291. context2D.save();
  292. context2D.globalCompositeOperation = 'destination-out';
  293. context2D.beginPath();
  294. context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true);
  295. context2D.closePath();
  296. context2D.fillStyle = 'black';
  297. context2D.fill();
  298. context2D.restore();
  299. }
  300. }
  301. context2D.beginPath();
  302. context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true);
  303. context2D.closePath();
  304. context2D.fillStyle = cssColor;
  305. context2D.fill();
  306. return canvas;
  307. };
  308. }
  309. return PointVisualizer;
  310. });