Polyline.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import arrayRemoveDuplicates from '../Core/arrayRemoveDuplicates.js';
  2. import BoundingSphere from '../Core/BoundingSphere.js';
  3. import Cartesian3 from '../Core/Cartesian3.js';
  4. import Color from '../Core/Color.js';
  5. import defaultValue from '../Core/defaultValue.js';
  6. import defined from '../Core/defined.js';
  7. import defineProperties from '../Core/defineProperties.js';
  8. import DeveloperError from '../Core/DeveloperError.js';
  9. import DistanceDisplayCondition from '../Core/DistanceDisplayCondition.js';
  10. import Matrix4 from '../Core/Matrix4.js';
  11. import PolylinePipeline from '../Core/PolylinePipeline.js';
  12. import Material from './Material.js';
  13. /**
  14. * A renderable polyline. Create this by calling {@link PolylineCollection#add}
  15. *
  16. * @alias Polyline
  17. * @internalConstructor
  18. * @class
  19. *
  20. * @param {Object} [options] Object with the following properties:
  21. * @param {Boolean} [options.show=true] <code>true</code> if this polyline will be shown; otherwise, <code>false</code>.
  22. * @param {Number} [options.width=1.0] The width of the polyline in pixels.
  23. * @param {Boolean} [options.loop=false] Whether a line segment will be added between the last and first line positions to make this line a loop.
  24. * @param {Material} [options.material=Material.ColorType] The material.
  25. * @param {Cartesian3[]} [options.positions] The positions.
  26. * @param {Object} [options.id] The user-defined object to be returned when this polyline is picked.
  27. * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this polyline will be displayed.
  28. * @param {PolylineCollection} polylineCollection The renderable polyline collection.
  29. *
  30. * @see PolylineCollection
  31. *
  32. */
  33. function Polyline(options, polylineCollection) {
  34. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  35. this._show = defaultValue(options.show, true);
  36. this._width = defaultValue(options.width, 1.0);
  37. this._loop = defaultValue(options.loop, false);
  38. this._distanceDisplayCondition = options.distanceDisplayCondition;
  39. this._material = options.material;
  40. if (!defined(this._material)) {
  41. this._material = Material.fromType(Material.ColorType, {
  42. color : new Color(1.0, 1.0, 1.0, 1.0)
  43. });
  44. }
  45. var positions = options.positions;
  46. if (!defined(positions)) {
  47. positions = [];
  48. }
  49. this._positions = positions;
  50. this._actualPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
  51. if (this._loop && this._actualPositions.length > 2) {
  52. if (this._actualPositions === this._positions) {
  53. this._actualPositions = positions.slice();
  54. }
  55. this._actualPositions.push(Cartesian3.clone(this._actualPositions[0]));
  56. }
  57. this._length = this._actualPositions.length;
  58. this._id = options.id;
  59. var modelMatrix;
  60. if (defined(polylineCollection)) {
  61. modelMatrix = Matrix4.clone(polylineCollection.modelMatrix);
  62. }
  63. this._modelMatrix = modelMatrix;
  64. this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix);
  65. this._actualLength = undefined;
  66. this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES); //eslint-disable-line no-use-before-define
  67. this._polylineCollection = polylineCollection;
  68. this._dirty = false;
  69. this._pickId = undefined;
  70. this._boundingVolume = BoundingSphere.fromPoints(this._actualPositions);
  71. this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, this._modelMatrix);
  72. this._boundingVolume2D = new BoundingSphere(); // modified in PolylineCollection
  73. }
  74. var POSITION_INDEX = Polyline.POSITION_INDEX = 0;
  75. var SHOW_INDEX = Polyline.SHOW_INDEX = 1;
  76. var WIDTH_INDEX = Polyline.WIDTH_INDEX = 2;
  77. var MATERIAL_INDEX = Polyline.MATERIAL_INDEX = 3;
  78. var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX = 4;
  79. var DISTANCE_DISPLAY_CONDITION = Polyline.DISTANCE_DISPLAY_CONDITION = 5;
  80. var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES = 6;
  81. function makeDirty(polyline, propertyChanged) {
  82. ++polyline._propertiesChanged[propertyChanged];
  83. var polylineCollection = polyline._polylineCollection;
  84. if (defined(polylineCollection)) {
  85. polylineCollection._updatePolyline(polyline, propertyChanged);
  86. polyline._dirty = true;
  87. }
  88. }
  89. defineProperties(Polyline.prototype, {
  90. /**
  91. * Determines if this polyline will be shown. Use this to hide or show a polyline, instead
  92. * of removing it and re-adding it to the collection.
  93. * @memberof Polyline.prototype
  94. * @type {Boolean}
  95. */
  96. show: {
  97. get: function() {
  98. return this._show;
  99. },
  100. set: function(value) {
  101. //>>includeStart('debug', pragmas.debug);
  102. if (!defined(value)) {
  103. throw new DeveloperError('value is required.');
  104. }
  105. //>>includeEnd('debug');
  106. if (value !== this._show) {
  107. this._show = value;
  108. makeDirty(this, SHOW_INDEX);
  109. }
  110. }
  111. },
  112. /**
  113. * Gets or sets the positions of the polyline.
  114. * @memberof Polyline.prototype
  115. * @type {Cartesian3[]}
  116. * @example
  117. * polyline.positions = Cesium.Cartesian3.fromDegreesArray([
  118. * 0.0, 0.0,
  119. * 10.0, 0.0,
  120. * 0.0, 20.0
  121. * ]);
  122. */
  123. positions : {
  124. get: function() {
  125. return this._positions;
  126. },
  127. set: function(value) {
  128. //>>includeStart('debug', pragmas.debug);
  129. if (!defined(value)) {
  130. throw new DeveloperError('value is required.');
  131. }
  132. //>>includeEnd('debug');
  133. var positions = arrayRemoveDuplicates(value, Cartesian3.equalsEpsilon);
  134. if (this._loop && positions.length > 2) {
  135. if (positions === value) {
  136. positions = value.slice();
  137. }
  138. positions.push(Cartesian3.clone(positions[0]));
  139. }
  140. if (this._actualPositions.length !== positions.length || this._actualPositions.length !== this._length) {
  141. makeDirty(this, POSITION_SIZE_INDEX);
  142. }
  143. this._positions = value;
  144. this._actualPositions = positions;
  145. this._length = positions.length;
  146. this._boundingVolume = BoundingSphere.fromPoints(this._actualPositions, this._boundingVolume);
  147. this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, this._modelMatrix, this._boundingVolumeWC);
  148. makeDirty(this, POSITION_INDEX);
  149. this.update();
  150. }
  151. },
  152. /**
  153. * Gets or sets the surface appearance of the polyline. This can be one of several built-in {@link Material} objects or a custom material, scripted with
  154. * {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
  155. * @memberof Polyline.prototype
  156. * @type {Material}
  157. */
  158. material: {
  159. get: function() {
  160. return this._material;
  161. },
  162. set: function(material) {
  163. //>>includeStart('debug', pragmas.debug);
  164. if (!defined(material)) {
  165. throw new DeveloperError('material is required.');
  166. }
  167. //>>includeEnd('debug');
  168. if (this._material !== material) {
  169. this._material = material;
  170. makeDirty(this, MATERIAL_INDEX);
  171. }
  172. }
  173. },
  174. /**
  175. * Gets or sets the width of the polyline.
  176. * @memberof Polyline.prototype
  177. * @type {Number}
  178. */
  179. width: {
  180. get: function() {
  181. return this._width;
  182. },
  183. set: function(value) {
  184. //>>includeStart('debug', pragmas.debug)
  185. if (!defined(value)) {
  186. throw new DeveloperError('value is required.');
  187. }
  188. //>>includeEnd('debug');
  189. var width = this._width;
  190. if (value !== width) {
  191. this._width = value;
  192. makeDirty(this, WIDTH_INDEX);
  193. }
  194. }
  195. },
  196. /**
  197. * Gets or sets whether a line segment will be added between the first and last polyline positions.
  198. * @memberof Polyline.prototype
  199. * @type {Boolean}
  200. */
  201. loop: {
  202. get: function() {
  203. return this._loop;
  204. },
  205. set: function(value) {
  206. //>>includeStart('debug', pragmas.debug)
  207. if (!defined(value)) {
  208. throw new DeveloperError('value is required.');
  209. }
  210. //>>includeEnd('debug');
  211. if (value !== this._loop) {
  212. var positions = this._actualPositions;
  213. if (value) {
  214. if (positions.length > 2 && !Cartesian3.equals(positions[0], positions[positions.length - 1])) {
  215. if (positions.length === this._positions.length) {
  216. this._actualPositions = positions = this._positions.slice();
  217. }
  218. positions.push(Cartesian3.clone(positions[0]));
  219. }
  220. } else if (positions.length > 2 && Cartesian3.equals(positions[0], positions[positions.length - 1])) {
  221. if (positions.length - 1 === this._positions.length) {
  222. this._actualPositions = this._positions;
  223. } else {
  224. positions.pop();
  225. }
  226. }
  227. this._loop = value;
  228. makeDirty(this, POSITION_SIZE_INDEX);
  229. }
  230. }
  231. },
  232. /**
  233. * Gets or sets the user-defined value returned when the polyline is picked.
  234. * @memberof Polyline.prototype
  235. * @type {*}
  236. */
  237. id : {
  238. get : function() {
  239. return this._id;
  240. },
  241. set : function(value) {
  242. this._id = value;
  243. if (defined(this._pickId)) {
  244. this._pickId.object.id = value;
  245. }
  246. }
  247. },
  248. /**
  249. * @private
  250. */
  251. pickId : {
  252. get : function() {
  253. return this._pickId;
  254. }
  255. },
  256. /**
  257. * Gets or sets the condition specifying at what distance from the camera that this polyline will be displayed.
  258. * @memberof Polyline.prototype
  259. * @type {DistanceDisplayCondition}
  260. * @default undefined
  261. */
  262. distanceDisplayCondition : {
  263. get : function() {
  264. return this._distanceDisplayCondition;
  265. },
  266. set : function(value) {
  267. //>>includeStart('debug', pragmas.debug);
  268. if (defined(value) && value.far <= value.near) {
  269. throw new DeveloperError('far distance must be greater than near distance.');
  270. }
  271. //>>includeEnd('debug');
  272. if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) {
  273. this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
  274. makeDirty(this, DISTANCE_DISPLAY_CONDITION);
  275. }
  276. }
  277. }
  278. });
  279. /**
  280. * @private
  281. */
  282. Polyline.prototype.update = function() {
  283. var modelMatrix = Matrix4.IDENTITY;
  284. if (defined(this._polylineCollection)) {
  285. modelMatrix = this._polylineCollection.modelMatrix;
  286. }
  287. var segmentPositionsLength = this._segments.positions.length;
  288. var segmentLengths = this._segments.lengths;
  289. var positionsChanged = this._propertiesChanged[POSITION_INDEX] > 0 || this._propertiesChanged[POSITION_SIZE_INDEX] > 0;
  290. if (!Matrix4.equals(modelMatrix, this._modelMatrix) || positionsChanged) {
  291. this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix);
  292. this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, modelMatrix, this._boundingVolumeWC);
  293. }
  294. this._modelMatrix = Matrix4.clone(modelMatrix, this._modelMatrix);
  295. if (this._segments.positions.length !== segmentPositionsLength) {
  296. // number of positions changed
  297. makeDirty(this, POSITION_SIZE_INDEX);
  298. } else {
  299. var length = segmentLengths.length;
  300. for (var i = 0; i < length; ++i) {
  301. if (segmentLengths[i] !== this._segments.lengths[i]) {
  302. // indices changed
  303. makeDirty(this, POSITION_SIZE_INDEX);
  304. break;
  305. }
  306. }
  307. }
  308. };
  309. /**
  310. * @private
  311. */
  312. Polyline.prototype.getPickId = function(context) {
  313. if (!defined(this._pickId)) {
  314. this._pickId = context.createPickId({
  315. primitive : this,
  316. collection : this._polylineCollection,
  317. id : this._id
  318. });
  319. }
  320. return this._pickId;
  321. };
  322. Polyline.prototype._clean = function() {
  323. this._dirty = false;
  324. var properties = this._propertiesChanged;
  325. for ( var k = 0; k < NUMBER_OF_PROPERTIES - 1; ++k) {
  326. properties[k] = 0;
  327. }
  328. };
  329. Polyline.prototype._destroy = function() {
  330. this._pickId = this._pickId && this._pickId.destroy();
  331. this._material = this._material && this._material.destroy();
  332. this._polylineCollection = undefined;
  333. };
  334. export default Polyline;