PathVisualizer.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. import AssociativeArray from '../Core/AssociativeArray.js';
  2. import Cartesian3 from '../Core/Cartesian3.js';
  3. import defined from '../Core/defined.js';
  4. import destroyObject from '../Core/destroyObject.js';
  5. import DeveloperError from '../Core/DeveloperError.js';
  6. import JulianDate from '../Core/JulianDate.js';
  7. import Matrix3 from '../Core/Matrix3.js';
  8. import Matrix4 from '../Core/Matrix4.js';
  9. import ReferenceFrame from '../Core/ReferenceFrame.js';
  10. import TimeInterval from '../Core/TimeInterval.js';
  11. import Transforms from '../Core/Transforms.js';
  12. import PolylineCollection from '../Scene/PolylineCollection.js';
  13. import SceneMode from '../Scene/SceneMode.js';
  14. import CompositePositionProperty from './CompositePositionProperty.js';
  15. import ConstantPositionProperty from './ConstantPositionProperty.js';
  16. import MaterialProperty from './MaterialProperty.js';
  17. import Property from './Property.js';
  18. import ReferenceProperty from './ReferenceProperty.js';
  19. import SampledPositionProperty from './SampledPositionProperty.js';
  20. import ScaledPositionProperty from './ScaledPositionProperty.js';
  21. import TimeIntervalCollectionPositionProperty from './TimeIntervalCollectionPositionProperty.js';
  22. var defaultResolution = 60.0;
  23. var defaultWidth = 1.0;
  24. var scratchTimeInterval = new TimeInterval();
  25. var subSampleCompositePropertyScratch = new TimeInterval();
  26. var subSampleIntervalPropertyScratch = new TimeInterval();
  27. function EntityData(entity) {
  28. this.entity = entity;
  29. this.polyline = undefined;
  30. this.index = undefined;
  31. this.updater = undefined;
  32. }
  33. function subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  34. var r = startingIndex;
  35. //Always step exactly on start (but only use it if it exists.)
  36. var tmp;
  37. tmp = property.getValueInReferenceFrame(start, referenceFrame, result[r]);
  38. if (defined(tmp)) {
  39. result[r++] = tmp;
  40. }
  41. var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
  42. //Iterate over all interval times and add the ones that fall in our
  43. //time range. Note that times can contain data outside of
  44. //the intervals range. This is by design for use with interpolation.
  45. var t = 0;
  46. var len = times.length;
  47. var current = times[t];
  48. var loopStop = stop;
  49. var sampling = false;
  50. var sampleStepsToTake;
  51. var sampleStepsTaken;
  52. var sampleStepSize;
  53. while (t < len) {
  54. if (!steppedOnNow && JulianDate.greaterThanOrEquals(current, updateTime)) {
  55. tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[r]);
  56. if (defined(tmp)) {
  57. result[r++] = tmp;
  58. }
  59. steppedOnNow = true;
  60. }
  61. if (JulianDate.greaterThan(current, start) && JulianDate.lessThan(current, loopStop) && !current.equals(updateTime)) {
  62. tmp = property.getValueInReferenceFrame(current, referenceFrame, result[r]);
  63. if (defined(tmp)) {
  64. result[r++] = tmp;
  65. }
  66. }
  67. if (t < (len - 1)) {
  68. if (maximumStep > 0 && !sampling) {
  69. var next = times[t + 1];
  70. var secondsUntilNext = JulianDate.secondsDifference(next, current);
  71. sampling = secondsUntilNext > maximumStep;
  72. if (sampling) {
  73. sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep);
  74. sampleStepsTaken = 0;
  75. sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2);
  76. sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1);
  77. }
  78. }
  79. if (sampling && sampleStepsTaken < sampleStepsToTake) {
  80. current = JulianDate.addSeconds(current, sampleStepSize, new JulianDate());
  81. sampleStepsTaken++;
  82. continue;
  83. }
  84. }
  85. sampling = false;
  86. t++;
  87. current = times[t];
  88. }
  89. //Always step exactly on stop (but only use it if it exists.)
  90. tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[r]);
  91. if (defined(tmp)) {
  92. result[r++] = tmp;
  93. }
  94. return r;
  95. }
  96. function subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  97. var tmp;
  98. var i = 0;
  99. var index = startingIndex;
  100. var time = start;
  101. var stepSize = Math.max(maximumStep, 60);
  102. var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
  103. while (JulianDate.lessThan(time, stop)) {
  104. if (!steppedOnNow && JulianDate.greaterThanOrEquals(time, updateTime)) {
  105. steppedOnNow = true;
  106. tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[index]);
  107. if (defined(tmp)) {
  108. result[index] = tmp;
  109. index++;
  110. }
  111. }
  112. tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
  113. if (defined(tmp)) {
  114. result[index] = tmp;
  115. index++;
  116. }
  117. i++;
  118. time = JulianDate.addSeconds(start, stepSize * i, new JulianDate());
  119. }
  120. //Always sample stop.
  121. tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[index]);
  122. if (defined(tmp)) {
  123. result[index] = tmp;
  124. index++;
  125. }
  126. return index;
  127. }
  128. function subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  129. subSampleIntervalPropertyScratch.start = start;
  130. subSampleIntervalPropertyScratch.stop = stop;
  131. var index = startingIndex;
  132. var intervals = property.intervals;
  133. for (var i = 0; i < intervals.length; i++) {
  134. var interval = intervals.get(i);
  135. if (!TimeInterval.intersect(interval, subSampleIntervalPropertyScratch, scratchTimeInterval).isEmpty) {
  136. var time = interval.start;
  137. if (!interval.isStartIncluded) {
  138. if (interval.isStopIncluded) {
  139. time = interval.stop;
  140. } else {
  141. time = JulianDate.addSeconds(interval.start, JulianDate.secondsDifference(interval.stop, interval.start) / 2, new JulianDate());
  142. }
  143. }
  144. var tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
  145. if (defined(tmp)) {
  146. result[index] = tmp;
  147. index++;
  148. }
  149. }
  150. }
  151. return index;
  152. }
  153. function subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  154. var tmp = property.getValueInReferenceFrame(start, referenceFrame, result[startingIndex]);
  155. if (defined(tmp)) {
  156. result[startingIndex++] = tmp;
  157. }
  158. return startingIndex;
  159. }
  160. function subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
  161. subSampleCompositePropertyScratch.start = start;
  162. subSampleCompositePropertyScratch.stop = stop;
  163. var index = startingIndex;
  164. var intervals = property.intervals;
  165. for (var i = 0; i < intervals.length; i++) {
  166. var interval = intervals.get(i);
  167. if (!TimeInterval.intersect(interval, subSampleCompositePropertyScratch, scratchTimeInterval).isEmpty) {
  168. var intervalStart = interval.start;
  169. var intervalStop = interval.stop;
  170. var sampleStart = start;
  171. if (JulianDate.greaterThan(intervalStart, sampleStart)) {
  172. sampleStart = intervalStart;
  173. }
  174. var sampleStop = stop;
  175. if (JulianDate.lessThan(intervalStop, sampleStop)) {
  176. sampleStop = intervalStop;
  177. }
  178. index = reallySubSample(interval.data, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
  179. }
  180. }
  181. return index;
  182. }
  183. function reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, index, result) {
  184. //Unwrap any references until we have the actual property.
  185. while (property instanceof ReferenceProperty) {
  186. property = property.resolvedProperty;
  187. }
  188. if (property instanceof SampledPositionProperty) {
  189. var times = property._property._times;
  190. index = subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, index, result);
  191. } else if (property instanceof CompositePositionProperty) {
  192. index = subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
  193. } else if (property instanceof TimeIntervalCollectionPositionProperty) {
  194. index = subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
  195. } else if (property instanceof ConstantPositionProperty ||
  196. (property instanceof ScaledPositionProperty && Property.isConstant(property))) {
  197. index = subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
  198. } else {
  199. //Fallback to generic sampling.
  200. index = subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
  201. }
  202. return index;
  203. }
  204. function subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result) {
  205. if (!defined(result)) {
  206. result = [];
  207. }
  208. var length = reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
  209. result.length = length;
  210. return result;
  211. }
  212. var toFixedScratch = new Matrix3();
  213. function PolylineUpdater(scene, referenceFrame) {
  214. this._unusedIndexes = [];
  215. this._polylineCollection = new PolylineCollection();
  216. this._scene = scene;
  217. this._referenceFrame = referenceFrame;
  218. scene.primitives.add(this._polylineCollection);
  219. }
  220. PolylineUpdater.prototype.update = function(time) {
  221. if (this._referenceFrame === ReferenceFrame.INERTIAL) {
  222. var toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch);
  223. if (!defined(toFixed)) {
  224. toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, toFixedScratch);
  225. }
  226. Matrix4.fromRotationTranslation(toFixed, Cartesian3.ZERO, this._polylineCollection.modelMatrix);
  227. }
  228. };
  229. PolylineUpdater.prototype.updateObject = function(time, item) {
  230. var entity = item.entity;
  231. var pathGraphics = entity._path;
  232. var positionProperty = entity._position;
  233. var sampleStart;
  234. var sampleStop;
  235. var showProperty = pathGraphics._show;
  236. var polyline = item.polyline;
  237. var show = entity.isShowing && (!defined(showProperty) || showProperty.getValue(time));
  238. //While we want to show the path, there may not actually be anything to show
  239. //depending on lead/trail settings. Compute the interval of the path to
  240. //show and check against actual availability.
  241. if (show) {
  242. var leadTime = Property.getValueOrUndefined(pathGraphics._leadTime, time);
  243. var trailTime = Property.getValueOrUndefined(pathGraphics._trailTime, time);
  244. var availability = entity._availability;
  245. var hasAvailability = defined(availability);
  246. var hasLeadTime = defined(leadTime);
  247. var hasTrailTime = defined(trailTime);
  248. //Objects need to have either defined availability or both a lead and trail time in order to
  249. //draw a path (since we can't draw "infinite" paths.
  250. show = hasAvailability || (hasLeadTime && hasTrailTime);
  251. //The final step is to compute the actual start/stop times of the path to show.
  252. //If current time is outside of the availability interval, there's a chance that
  253. //we won't have to draw anything anyway.
  254. if (show) {
  255. if (hasTrailTime) {
  256. sampleStart = JulianDate.addSeconds(time, -trailTime, new JulianDate());
  257. }
  258. if (hasLeadTime) {
  259. sampleStop = JulianDate.addSeconds(time, leadTime, new JulianDate());
  260. }
  261. if (hasAvailability) {
  262. var start = availability.start;
  263. var stop = availability.stop;
  264. if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) {
  265. sampleStart = start;
  266. }
  267. if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) {
  268. sampleStop = stop;
  269. }
  270. }
  271. show = JulianDate.lessThan(sampleStart, sampleStop);
  272. }
  273. }
  274. if (!show) {
  275. //don't bother creating or updating anything else
  276. if (defined(polyline)) {
  277. this._unusedIndexes.push(item.index);
  278. item.polyline = undefined;
  279. polyline.show = false;
  280. item.index = undefined;
  281. }
  282. return;
  283. }
  284. if (!defined(polyline)) {
  285. var unusedIndexes = this._unusedIndexes;
  286. var length = unusedIndexes.length;
  287. if (length > 0) {
  288. var index = unusedIndexes.pop();
  289. polyline = this._polylineCollection.get(index);
  290. item.index = index;
  291. } else {
  292. item.index = this._polylineCollection.length;
  293. polyline = this._polylineCollection.add();
  294. }
  295. polyline.id = entity;
  296. item.polyline = polyline;
  297. }
  298. var resolution = Property.getValueOrDefault(pathGraphics._resolution, time, defaultResolution);
  299. polyline.show = true;
  300. polyline.positions = subSample(positionProperty, sampleStart, sampleStop, time, this._referenceFrame, resolution, polyline.positions.slice());
  301. polyline.material = MaterialProperty.getValue(time, pathGraphics._material, polyline.material);
  302. polyline.width = Property.getValueOrDefault(pathGraphics._width, time, defaultWidth);
  303. polyline.distanceDisplayCondition = Property.getValueOrUndefined(pathGraphics._distanceDisplayCondition, time, polyline.distanceDisplayCondition);
  304. };
  305. PolylineUpdater.prototype.removeObject = function(item) {
  306. var polyline = item.polyline;
  307. if (defined(polyline)) {
  308. this._unusedIndexes.push(item.index);
  309. item.polyline = undefined;
  310. polyline.show = false;
  311. polyline.id = undefined;
  312. item.index = undefined;
  313. }
  314. };
  315. PolylineUpdater.prototype.destroy = function() {
  316. this._scene.primitives.remove(this._polylineCollection);
  317. return destroyObject(this);
  318. };
  319. /**
  320. * A {@link Visualizer} which maps {@link Entity#path} to a {@link Polyline}.
  321. * @alias PathVisualizer
  322. * @constructor
  323. *
  324. * @param {Scene} scene The scene the primitives will be rendered in.
  325. * @param {EntityCollection} entityCollection The entityCollection to visualize.
  326. */
  327. function PathVisualizer(scene, entityCollection) {
  328. //>>includeStart('debug', pragmas.debug);
  329. if (!defined(scene)) {
  330. throw new DeveloperError('scene is required.');
  331. }
  332. if (!defined(entityCollection)) {
  333. throw new DeveloperError('entityCollection is required.');
  334. }
  335. //>>includeEnd('debug');
  336. entityCollection.collectionChanged.addEventListener(PathVisualizer.prototype._onCollectionChanged, this);
  337. this._scene = scene;
  338. this._updaters = {};
  339. this._entityCollection = entityCollection;
  340. this._items = new AssociativeArray();
  341. this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
  342. }
  343. /**
  344. * Updates all of the primitives created by this visualizer to match their
  345. * Entity counterpart at the given time.
  346. *
  347. * @param {JulianDate} time The time to update to.
  348. * @returns {Boolean} This function always returns true.
  349. */
  350. PathVisualizer.prototype.update = function(time) {
  351. //>>includeStart('debug', pragmas.debug);
  352. if (!defined(time)) {
  353. throw new DeveloperError('time is required.');
  354. }
  355. //>>includeEnd('debug');
  356. var updaters = this._updaters;
  357. for (var key in updaters) {
  358. if (updaters.hasOwnProperty(key)) {
  359. updaters[key].update(time);
  360. }
  361. }
  362. var items = this._items.values;
  363. if (items.length === 0 && defined(this._updaters) && Object.keys(this._updaters).length > 0) {
  364. for (var u in updaters) {
  365. if (updaters.hasOwnProperty(u)) {
  366. updaters[u].destroy();
  367. }
  368. }
  369. this._updaters = {};
  370. }
  371. for (var i = 0, len = items.length; i < len; i++) {
  372. var item = items[i];
  373. var entity = item.entity;
  374. var positionProperty = entity._position;
  375. var lastUpdater = item.updater;
  376. var frameToVisualize = ReferenceFrame.FIXED;
  377. if (this._scene.mode === SceneMode.SCENE3D) {
  378. frameToVisualize = positionProperty.referenceFrame;
  379. }
  380. var currentUpdater = this._updaters[frameToVisualize];
  381. if ((lastUpdater === currentUpdater) && (defined(currentUpdater))) {
  382. currentUpdater.updateObject(time, item);
  383. continue;
  384. }
  385. if (defined(lastUpdater)) {
  386. lastUpdater.removeObject(item);
  387. }
  388. if (!defined(currentUpdater)) {
  389. currentUpdater = new PolylineUpdater(this._scene, frameToVisualize);
  390. currentUpdater.update(time);
  391. this._updaters[frameToVisualize] = currentUpdater;
  392. }
  393. item.updater = currentUpdater;
  394. if (defined(currentUpdater)) {
  395. currentUpdater.updateObject(time, item);
  396. }
  397. }
  398. return true;
  399. };
  400. /**
  401. * Returns true if this object was destroyed; otherwise, false.
  402. *
  403. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  404. */
  405. PathVisualizer.prototype.isDestroyed = function() {
  406. return false;
  407. };
  408. /**
  409. * Removes and destroys all primitives created by this instance.
  410. */
  411. PathVisualizer.prototype.destroy = function() {
  412. this._entityCollection.collectionChanged.removeEventListener(PathVisualizer.prototype._onCollectionChanged, this);
  413. var updaters = this._updaters;
  414. for ( var key in updaters) {
  415. if (updaters.hasOwnProperty(key)) {
  416. updaters[key].destroy();
  417. }
  418. }
  419. return destroyObject(this);
  420. };
  421. PathVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
  422. var i;
  423. var entity;
  424. var item;
  425. var items = this._items;
  426. for (i = added.length - 1; i > -1; i--) {
  427. entity = added[i];
  428. if (defined(entity._path) && defined(entity._position)) {
  429. items.set(entity.id, new EntityData(entity));
  430. }
  431. }
  432. for (i = changed.length - 1; i > -1; i--) {
  433. entity = changed[i];
  434. if (defined(entity._path) && defined(entity._position)) {
  435. if (!items.contains(entity.id)) {
  436. items.set(entity.id, new EntityData(entity));
  437. }
  438. } else {
  439. item = items.get(entity.id);
  440. if (defined(item)) {
  441. if (defined(item.updater)) {
  442. item.updater.removeObject(item);
  443. }
  444. items.remove(entity.id);
  445. }
  446. }
  447. }
  448. for (i = removed.length - 1; i > -1; i--) {
  449. entity = removed[i];
  450. item = items.get(entity.id);
  451. if (defined(item)) {
  452. if (defined(item.updater)) {
  453. item.updater.removeObject(item);
  454. }
  455. items.remove(entity.id);
  456. }
  457. }
  458. };
  459. //for testing
  460. PathVisualizer._subSample = subSample;
  461. export default PathVisualizer;