PathVisualizer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  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(
  34. property,
  35. start,
  36. stop,
  37. times,
  38. updateTime,
  39. referenceFrame,
  40. maximumStep,
  41. startingIndex,
  42. result
  43. ) {
  44. var r = startingIndex;
  45. //Always step exactly on start (but only use it if it exists.)
  46. var tmp;
  47. tmp = property.getValueInReferenceFrame(start, referenceFrame, result[r]);
  48. if (defined(tmp)) {
  49. result[r++] = tmp;
  50. }
  51. var steppedOnNow =
  52. !defined(updateTime) ||
  53. JulianDate.lessThanOrEquals(updateTime, start) ||
  54. JulianDate.greaterThanOrEquals(updateTime, stop);
  55. //Iterate over all interval times and add the ones that fall in our
  56. //time range. Note that times can contain data outside of
  57. //the intervals range. This is by design for use with interpolation.
  58. var t = 0;
  59. var len = times.length;
  60. var current = times[t];
  61. var loopStop = stop;
  62. var sampling = false;
  63. var sampleStepsToTake;
  64. var sampleStepsTaken;
  65. var sampleStepSize;
  66. while (t < len) {
  67. if (!steppedOnNow && JulianDate.greaterThanOrEquals(current, updateTime)) {
  68. tmp = property.getValueInReferenceFrame(
  69. updateTime,
  70. referenceFrame,
  71. result[r]
  72. );
  73. if (defined(tmp)) {
  74. result[r++] = tmp;
  75. }
  76. steppedOnNow = true;
  77. }
  78. if (
  79. JulianDate.greaterThan(current, start) &&
  80. JulianDate.lessThan(current, loopStop) &&
  81. !current.equals(updateTime)
  82. ) {
  83. tmp = property.getValueInReferenceFrame(
  84. current,
  85. referenceFrame,
  86. result[r]
  87. );
  88. if (defined(tmp)) {
  89. result[r++] = tmp;
  90. }
  91. }
  92. if (t < len - 1) {
  93. if (maximumStep > 0 && !sampling) {
  94. var next = times[t + 1];
  95. var secondsUntilNext = JulianDate.secondsDifference(next, current);
  96. sampling = secondsUntilNext > maximumStep;
  97. if (sampling) {
  98. sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep);
  99. sampleStepsTaken = 0;
  100. sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2);
  101. sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1);
  102. }
  103. }
  104. if (sampling && sampleStepsTaken < sampleStepsToTake) {
  105. current = JulianDate.addSeconds(
  106. current,
  107. sampleStepSize,
  108. new JulianDate()
  109. );
  110. sampleStepsTaken++;
  111. continue;
  112. }
  113. }
  114. sampling = false;
  115. t++;
  116. current = times[t];
  117. }
  118. //Always step exactly on stop (but only use it if it exists.)
  119. tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[r]);
  120. if (defined(tmp)) {
  121. result[r++] = tmp;
  122. }
  123. return r;
  124. }
  125. function subSampleGenericProperty(
  126. property,
  127. start,
  128. stop,
  129. updateTime,
  130. referenceFrame,
  131. maximumStep,
  132. startingIndex,
  133. result
  134. ) {
  135. var tmp;
  136. var i = 0;
  137. var index = startingIndex;
  138. var time = start;
  139. var stepSize = Math.max(maximumStep, 60);
  140. var steppedOnNow =
  141. !defined(updateTime) ||
  142. JulianDate.lessThanOrEquals(updateTime, start) ||
  143. JulianDate.greaterThanOrEquals(updateTime, stop);
  144. while (JulianDate.lessThan(time, stop)) {
  145. if (!steppedOnNow && JulianDate.greaterThanOrEquals(time, updateTime)) {
  146. steppedOnNow = true;
  147. tmp = property.getValueInReferenceFrame(
  148. updateTime,
  149. referenceFrame,
  150. result[index]
  151. );
  152. if (defined(tmp)) {
  153. result[index] = tmp;
  154. index++;
  155. }
  156. }
  157. tmp = property.getValueInReferenceFrame(
  158. time,
  159. referenceFrame,
  160. result[index]
  161. );
  162. if (defined(tmp)) {
  163. result[index] = tmp;
  164. index++;
  165. }
  166. i++;
  167. time = JulianDate.addSeconds(start, stepSize * i, new JulianDate());
  168. }
  169. //Always sample stop.
  170. tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[index]);
  171. if (defined(tmp)) {
  172. result[index] = tmp;
  173. index++;
  174. }
  175. return index;
  176. }
  177. function subSampleIntervalProperty(
  178. property,
  179. start,
  180. stop,
  181. updateTime,
  182. referenceFrame,
  183. maximumStep,
  184. startingIndex,
  185. result
  186. ) {
  187. subSampleIntervalPropertyScratch.start = start;
  188. subSampleIntervalPropertyScratch.stop = stop;
  189. var index = startingIndex;
  190. var intervals = property.intervals;
  191. for (var i = 0; i < intervals.length; i++) {
  192. var interval = intervals.get(i);
  193. if (
  194. !TimeInterval.intersect(
  195. interval,
  196. subSampleIntervalPropertyScratch,
  197. scratchTimeInterval
  198. ).isEmpty
  199. ) {
  200. var time = interval.start;
  201. if (!interval.isStartIncluded) {
  202. if (interval.isStopIncluded) {
  203. time = interval.stop;
  204. } else {
  205. time = JulianDate.addSeconds(
  206. interval.start,
  207. JulianDate.secondsDifference(interval.stop, interval.start) / 2,
  208. new JulianDate()
  209. );
  210. }
  211. }
  212. var tmp = property.getValueInReferenceFrame(
  213. time,
  214. referenceFrame,
  215. result[index]
  216. );
  217. if (defined(tmp)) {
  218. result[index] = tmp;
  219. index++;
  220. }
  221. }
  222. }
  223. return index;
  224. }
  225. function subSampleConstantProperty(
  226. property,
  227. start,
  228. stop,
  229. updateTime,
  230. referenceFrame,
  231. maximumStep,
  232. startingIndex,
  233. result
  234. ) {
  235. var tmp = property.getValueInReferenceFrame(
  236. start,
  237. referenceFrame,
  238. result[startingIndex]
  239. );
  240. if (defined(tmp)) {
  241. result[startingIndex++] = tmp;
  242. }
  243. return startingIndex;
  244. }
  245. function subSampleCompositeProperty(
  246. property,
  247. start,
  248. stop,
  249. updateTime,
  250. referenceFrame,
  251. maximumStep,
  252. startingIndex,
  253. result
  254. ) {
  255. subSampleCompositePropertyScratch.start = start;
  256. subSampleCompositePropertyScratch.stop = stop;
  257. var index = startingIndex;
  258. var intervals = property.intervals;
  259. for (var i = 0; i < intervals.length; i++) {
  260. var interval = intervals.get(i);
  261. if (
  262. !TimeInterval.intersect(
  263. interval,
  264. subSampleCompositePropertyScratch,
  265. scratchTimeInterval
  266. ).isEmpty
  267. ) {
  268. var intervalStart = interval.start;
  269. var intervalStop = interval.stop;
  270. var sampleStart = start;
  271. if (JulianDate.greaterThan(intervalStart, sampleStart)) {
  272. sampleStart = intervalStart;
  273. }
  274. var sampleStop = stop;
  275. if (JulianDate.lessThan(intervalStop, sampleStop)) {
  276. sampleStop = intervalStop;
  277. }
  278. index = reallySubSample(
  279. interval.data,
  280. sampleStart,
  281. sampleStop,
  282. updateTime,
  283. referenceFrame,
  284. maximumStep,
  285. index,
  286. result
  287. );
  288. }
  289. }
  290. return index;
  291. }
  292. function reallySubSample(
  293. property,
  294. start,
  295. stop,
  296. updateTime,
  297. referenceFrame,
  298. maximumStep,
  299. index,
  300. result
  301. ) {
  302. //Unwrap any references until we have the actual property.
  303. while (property instanceof ReferenceProperty) {
  304. property = property.resolvedProperty;
  305. }
  306. if (property instanceof SampledPositionProperty) {
  307. var times = property._property._times;
  308. index = subSampleSampledProperty(
  309. property,
  310. start,
  311. stop,
  312. times,
  313. updateTime,
  314. referenceFrame,
  315. maximumStep,
  316. index,
  317. result
  318. );
  319. } else if (property instanceof CompositePositionProperty) {
  320. index = subSampleCompositeProperty(
  321. property,
  322. start,
  323. stop,
  324. updateTime,
  325. referenceFrame,
  326. maximumStep,
  327. index,
  328. result
  329. );
  330. } else if (property instanceof TimeIntervalCollectionPositionProperty) {
  331. index = subSampleIntervalProperty(
  332. property,
  333. start,
  334. stop,
  335. updateTime,
  336. referenceFrame,
  337. maximumStep,
  338. index,
  339. result
  340. );
  341. } else if (
  342. property instanceof ConstantPositionProperty ||
  343. (property instanceof ScaledPositionProperty &&
  344. Property.isConstant(property))
  345. ) {
  346. index = subSampleConstantProperty(
  347. property,
  348. start,
  349. stop,
  350. updateTime,
  351. referenceFrame,
  352. maximumStep,
  353. index,
  354. result
  355. );
  356. } else {
  357. //Fallback to generic sampling.
  358. index = subSampleGenericProperty(
  359. property,
  360. start,
  361. stop,
  362. updateTime,
  363. referenceFrame,
  364. maximumStep,
  365. index,
  366. result
  367. );
  368. }
  369. return index;
  370. }
  371. function subSample(
  372. property,
  373. start,
  374. stop,
  375. updateTime,
  376. referenceFrame,
  377. maximumStep,
  378. result
  379. ) {
  380. if (!defined(result)) {
  381. result = [];
  382. }
  383. var length = reallySubSample(
  384. property,
  385. start,
  386. stop,
  387. updateTime,
  388. referenceFrame,
  389. maximumStep,
  390. 0,
  391. result
  392. );
  393. result.length = length;
  394. return result;
  395. }
  396. var toFixedScratch = new Matrix3();
  397. function PolylineUpdater(scene, referenceFrame) {
  398. this._unusedIndexes = [];
  399. this._polylineCollection = new PolylineCollection();
  400. this._scene = scene;
  401. this._referenceFrame = referenceFrame;
  402. scene.primitives.add(this._polylineCollection);
  403. }
  404. PolylineUpdater.prototype.update = function (time) {
  405. if (this._referenceFrame === ReferenceFrame.INERTIAL) {
  406. var toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch);
  407. if (!defined(toFixed)) {
  408. toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, toFixedScratch);
  409. }
  410. Matrix4.fromRotationTranslation(
  411. toFixed,
  412. Cartesian3.ZERO,
  413. this._polylineCollection.modelMatrix
  414. );
  415. }
  416. };
  417. PolylineUpdater.prototype.updateObject = function (time, item) {
  418. var entity = item.entity;
  419. var pathGraphics = entity._path;
  420. var positionProperty = entity._position;
  421. var sampleStart;
  422. var sampleStop;
  423. var showProperty = pathGraphics._show;
  424. var polyline = item.polyline;
  425. var show =
  426. entity.isShowing && (!defined(showProperty) || showProperty.getValue(time));
  427. //While we want to show the path, there may not actually be anything to show
  428. //depending on lead/trail settings. Compute the interval of the path to
  429. //show and check against actual availability.
  430. if (show) {
  431. var leadTime = Property.getValueOrUndefined(pathGraphics._leadTime, time);
  432. var trailTime = Property.getValueOrUndefined(pathGraphics._trailTime, time);
  433. var availability = entity._availability;
  434. var hasAvailability = defined(availability);
  435. var hasLeadTime = defined(leadTime);
  436. var hasTrailTime = defined(trailTime);
  437. //Objects need to have either defined availability or both a lead and trail time in order to
  438. //draw a path (since we can't draw "infinite" paths.
  439. show = hasAvailability || (hasLeadTime && hasTrailTime);
  440. //The final step is to compute the actual start/stop times of the path to show.
  441. //If current time is outside of the availability interval, there's a chance that
  442. //we won't have to draw anything anyway.
  443. if (show) {
  444. if (hasTrailTime) {
  445. sampleStart = JulianDate.addSeconds(time, -trailTime, new JulianDate());
  446. }
  447. if (hasLeadTime) {
  448. sampleStop = JulianDate.addSeconds(time, leadTime, new JulianDate());
  449. }
  450. if (hasAvailability) {
  451. var start = availability.start;
  452. var stop = availability.stop;
  453. if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) {
  454. sampleStart = start;
  455. }
  456. if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) {
  457. sampleStop = stop;
  458. }
  459. }
  460. show = JulianDate.lessThan(sampleStart, sampleStop);
  461. }
  462. }
  463. if (!show) {
  464. //don't bother creating or updating anything else
  465. if (defined(polyline)) {
  466. this._unusedIndexes.push(item.index);
  467. item.polyline = undefined;
  468. polyline.show = false;
  469. item.index = undefined;
  470. }
  471. return;
  472. }
  473. if (!defined(polyline)) {
  474. var unusedIndexes = this._unusedIndexes;
  475. var length = unusedIndexes.length;
  476. if (length > 0) {
  477. var index = unusedIndexes.pop();
  478. polyline = this._polylineCollection.get(index);
  479. item.index = index;
  480. } else {
  481. item.index = this._polylineCollection.length;
  482. polyline = this._polylineCollection.add();
  483. }
  484. polyline.id = entity;
  485. item.polyline = polyline;
  486. }
  487. var resolution = Property.getValueOrDefault(
  488. pathGraphics._resolution,
  489. time,
  490. defaultResolution
  491. );
  492. polyline.show = true;
  493. polyline.positions = subSample(
  494. positionProperty,
  495. sampleStart,
  496. sampleStop,
  497. time,
  498. this._referenceFrame,
  499. resolution,
  500. polyline.positions.slice()
  501. );
  502. polyline.material = MaterialProperty.getValue(
  503. time,
  504. pathGraphics._material,
  505. polyline.material
  506. );
  507. polyline.width = Property.getValueOrDefault(
  508. pathGraphics._width,
  509. time,
  510. defaultWidth
  511. );
  512. polyline.distanceDisplayCondition = Property.getValueOrUndefined(
  513. pathGraphics._distanceDisplayCondition,
  514. time,
  515. polyline.distanceDisplayCondition
  516. );
  517. };
  518. PolylineUpdater.prototype.removeObject = function (item) {
  519. var polyline = item.polyline;
  520. if (defined(polyline)) {
  521. this._unusedIndexes.push(item.index);
  522. item.polyline = undefined;
  523. polyline.show = false;
  524. polyline.id = undefined;
  525. item.index = undefined;
  526. }
  527. };
  528. PolylineUpdater.prototype.destroy = function () {
  529. this._scene.primitives.remove(this._polylineCollection);
  530. return destroyObject(this);
  531. };
  532. /**
  533. * A {@link Visualizer} which maps {@link Entity#path} to a {@link Polyline}.
  534. * @alias PathVisualizer
  535. * @constructor
  536. *
  537. * @param {Scene} scene The scene the primitives will be rendered in.
  538. * @param {EntityCollection} entityCollection The entityCollection to visualize.
  539. */
  540. function PathVisualizer(scene, entityCollection) {
  541. //>>includeStart('debug', pragmas.debug);
  542. if (!defined(scene)) {
  543. throw new DeveloperError("scene is required.");
  544. }
  545. if (!defined(entityCollection)) {
  546. throw new DeveloperError("entityCollection is required.");
  547. }
  548. //>>includeEnd('debug');
  549. entityCollection.collectionChanged.addEventListener(
  550. PathVisualizer.prototype._onCollectionChanged,
  551. this
  552. );
  553. this._scene = scene;
  554. this._updaters = {};
  555. this._entityCollection = entityCollection;
  556. this._items = new AssociativeArray();
  557. this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
  558. }
  559. /**
  560. * Updates all of the primitives created by this visualizer to match their
  561. * Entity counterpart at the given time.
  562. *
  563. * @param {JulianDate} time The time to update to.
  564. * @returns {Boolean} This function always returns true.
  565. */
  566. PathVisualizer.prototype.update = function (time) {
  567. //>>includeStart('debug', pragmas.debug);
  568. if (!defined(time)) {
  569. throw new DeveloperError("time is required.");
  570. }
  571. //>>includeEnd('debug');
  572. var updaters = this._updaters;
  573. for (var key in updaters) {
  574. if (updaters.hasOwnProperty(key)) {
  575. updaters[key].update(time);
  576. }
  577. }
  578. var items = this._items.values;
  579. if (
  580. items.length === 0 &&
  581. defined(this._updaters) &&
  582. Object.keys(this._updaters).length > 0
  583. ) {
  584. for (var u in updaters) {
  585. if (updaters.hasOwnProperty(u)) {
  586. updaters[u].destroy();
  587. }
  588. }
  589. this._updaters = {};
  590. }
  591. for (var i = 0, len = items.length; i < len; i++) {
  592. var item = items[i];
  593. var entity = item.entity;
  594. var positionProperty = entity._position;
  595. var lastUpdater = item.updater;
  596. var frameToVisualize = ReferenceFrame.FIXED;
  597. if (this._scene.mode === SceneMode.SCENE3D) {
  598. frameToVisualize = positionProperty.referenceFrame;
  599. }
  600. var currentUpdater = this._updaters[frameToVisualize];
  601. if (lastUpdater === currentUpdater && defined(currentUpdater)) {
  602. currentUpdater.updateObject(time, item);
  603. continue;
  604. }
  605. if (defined(lastUpdater)) {
  606. lastUpdater.removeObject(item);
  607. }
  608. if (!defined(currentUpdater)) {
  609. currentUpdater = new PolylineUpdater(this._scene, frameToVisualize);
  610. currentUpdater.update(time);
  611. this._updaters[frameToVisualize] = currentUpdater;
  612. }
  613. item.updater = currentUpdater;
  614. if (defined(currentUpdater)) {
  615. currentUpdater.updateObject(time, item);
  616. }
  617. }
  618. return true;
  619. };
  620. /**
  621. * Returns true if this object was destroyed; otherwise, false.
  622. *
  623. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  624. */
  625. PathVisualizer.prototype.isDestroyed = function () {
  626. return false;
  627. };
  628. /**
  629. * Removes and destroys all primitives created by this instance.
  630. */
  631. PathVisualizer.prototype.destroy = function () {
  632. this._entityCollection.collectionChanged.removeEventListener(
  633. PathVisualizer.prototype._onCollectionChanged,
  634. this
  635. );
  636. var updaters = this._updaters;
  637. for (var key in updaters) {
  638. if (updaters.hasOwnProperty(key)) {
  639. updaters[key].destroy();
  640. }
  641. }
  642. return destroyObject(this);
  643. };
  644. PathVisualizer.prototype._onCollectionChanged = function (
  645. entityCollection,
  646. added,
  647. removed,
  648. changed
  649. ) {
  650. var i;
  651. var entity;
  652. var item;
  653. var items = this._items;
  654. for (i = added.length - 1; i > -1; i--) {
  655. entity = added[i];
  656. if (defined(entity._path) && defined(entity._position)) {
  657. items.set(entity.id, new EntityData(entity));
  658. }
  659. }
  660. for (i = changed.length - 1; i > -1; i--) {
  661. entity = changed[i];
  662. if (defined(entity._path) && defined(entity._position)) {
  663. if (!items.contains(entity.id)) {
  664. items.set(entity.id, new EntityData(entity));
  665. }
  666. } else {
  667. item = items.get(entity.id);
  668. if (defined(item)) {
  669. if (defined(item.updater)) {
  670. item.updater.removeObject(item);
  671. }
  672. items.remove(entity.id);
  673. }
  674. }
  675. }
  676. for (i = removed.length - 1; i > -1; i--) {
  677. entity = removed[i];
  678. item = items.get(entity.id);
  679. if (defined(item)) {
  680. if (defined(item.updater)) {
  681. item.updater.removeObject(item);
  682. }
  683. items.remove(entity.id);
  684. }
  685. }
  686. };
  687. //for testing
  688. PathVisualizer._subSample = subSample;
  689. export default PathVisualizer;