PathVisualizer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  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. const defaultResolution = 60.0;
  23. const defaultWidth = 1.0;
  24. const scratchTimeInterval = new TimeInterval();
  25. const subSampleCompositePropertyScratch = new TimeInterval();
  26. const 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. let r = startingIndex;
  45. //Always step exactly on start (but only use it if it exists.)
  46. let tmp;
  47. tmp = property.getValueInReferenceFrame(start, referenceFrame, result[r]);
  48. if (defined(tmp)) {
  49. result[r++] = tmp;
  50. }
  51. let 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. let t = 0;
  59. const len = times.length;
  60. let current = times[t];
  61. const loopStop = stop;
  62. let sampling = false;
  63. let sampleStepsToTake;
  64. let sampleStepsTaken;
  65. let 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. const next = times[t + 1];
  95. const 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. let tmp;
  136. let i = 0;
  137. let index = startingIndex;
  138. let time = start;
  139. const stepSize = Math.max(maximumStep, 60);
  140. let 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. let index = startingIndex;
  190. const intervals = property.intervals;
  191. for (let i = 0; i < intervals.length; i++) {
  192. const interval = intervals.get(i);
  193. if (
  194. !TimeInterval.intersect(
  195. interval,
  196. subSampleIntervalPropertyScratch,
  197. scratchTimeInterval
  198. ).isEmpty
  199. ) {
  200. let 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. const 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. const 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. let index = startingIndex;
  258. const intervals = property.intervals;
  259. for (let i = 0; i < intervals.length; i++) {
  260. const interval = intervals.get(i);
  261. if (
  262. !TimeInterval.intersect(
  263. interval,
  264. subSampleCompositePropertyScratch,
  265. scratchTimeInterval
  266. ).isEmpty
  267. ) {
  268. const intervalStart = interval.start;
  269. const intervalStop = interval.stop;
  270. let sampleStart = start;
  271. if (JulianDate.greaterThan(intervalStart, sampleStart)) {
  272. sampleStart = intervalStart;
  273. }
  274. let 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. const 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. const 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. const 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. let 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. const entity = item.entity;
  419. const pathGraphics = entity._path;
  420. const positionProperty = entity._position;
  421. let sampleStart;
  422. let sampleStop;
  423. const showProperty = pathGraphics._show;
  424. let polyline = item.polyline;
  425. let show =
  426. entity.isShowing &&
  427. entity.isAvailable(time) &&
  428. (!defined(showProperty) || showProperty.getValue(time));
  429. //While we want to show the path, there may not actually be anything to show
  430. //depending on lead/trail settings. Compute the interval of the path to
  431. //show and check against actual availability.
  432. if (show) {
  433. const leadTime = Property.getValueOrUndefined(pathGraphics._leadTime, time);
  434. const trailTime = Property.getValueOrUndefined(
  435. pathGraphics._trailTime,
  436. time
  437. );
  438. const availability = entity._availability;
  439. const hasAvailability = defined(availability);
  440. const hasLeadTime = defined(leadTime);
  441. const hasTrailTime = defined(trailTime);
  442. //Objects need to have either defined availability or both a lead and trail time in order to
  443. //draw a path (since we can't draw "infinite" paths.
  444. show = hasAvailability || (hasLeadTime && hasTrailTime);
  445. //The final step is to compute the actual start/stop times of the path to show.
  446. //If current time is outside of the availability interval, there's a chance that
  447. //we won't have to draw anything anyway.
  448. if (show) {
  449. if (hasTrailTime) {
  450. sampleStart = JulianDate.addSeconds(time, -trailTime, new JulianDate());
  451. }
  452. if (hasLeadTime) {
  453. sampleStop = JulianDate.addSeconds(time, leadTime, new JulianDate());
  454. }
  455. if (hasAvailability) {
  456. const start = availability.start;
  457. const stop = availability.stop;
  458. if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) {
  459. sampleStart = start;
  460. }
  461. if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) {
  462. sampleStop = stop;
  463. }
  464. }
  465. show = JulianDate.lessThan(sampleStart, sampleStop);
  466. }
  467. }
  468. if (!show) {
  469. //don't bother creating or updating anything else
  470. if (defined(polyline)) {
  471. this._unusedIndexes.push(item.index);
  472. item.polyline = undefined;
  473. polyline.show = false;
  474. item.index = undefined;
  475. }
  476. return;
  477. }
  478. if (!defined(polyline)) {
  479. const unusedIndexes = this._unusedIndexes;
  480. const length = unusedIndexes.length;
  481. if (length > 0) {
  482. const index = unusedIndexes.pop();
  483. polyline = this._polylineCollection.get(index);
  484. item.index = index;
  485. } else {
  486. item.index = this._polylineCollection.length;
  487. polyline = this._polylineCollection.add();
  488. }
  489. polyline.id = entity;
  490. item.polyline = polyline;
  491. }
  492. const resolution = Property.getValueOrDefault(
  493. pathGraphics._resolution,
  494. time,
  495. defaultResolution
  496. );
  497. polyline.show = true;
  498. polyline.positions = subSample(
  499. positionProperty,
  500. sampleStart,
  501. sampleStop,
  502. time,
  503. this._referenceFrame,
  504. resolution,
  505. polyline.positions.slice()
  506. );
  507. polyline.material = MaterialProperty.getValue(
  508. time,
  509. pathGraphics._material,
  510. polyline.material
  511. );
  512. polyline.width = Property.getValueOrDefault(
  513. pathGraphics._width,
  514. time,
  515. defaultWidth
  516. );
  517. polyline.distanceDisplayCondition = Property.getValueOrUndefined(
  518. pathGraphics._distanceDisplayCondition,
  519. time,
  520. polyline.distanceDisplayCondition
  521. );
  522. };
  523. PolylineUpdater.prototype.removeObject = function (item) {
  524. const polyline = item.polyline;
  525. if (defined(polyline)) {
  526. this._unusedIndexes.push(item.index);
  527. item.polyline = undefined;
  528. polyline.show = false;
  529. polyline.id = undefined;
  530. item.index = undefined;
  531. }
  532. };
  533. PolylineUpdater.prototype.destroy = function () {
  534. this._scene.primitives.remove(this._polylineCollection);
  535. return destroyObject(this);
  536. };
  537. /**
  538. * A {@link Visualizer} which maps {@link Entity#path} to a {@link Polyline}.
  539. * @alias PathVisualizer
  540. * @constructor
  541. *
  542. * @param {Scene} scene The scene the primitives will be rendered in.
  543. * @param {EntityCollection} entityCollection The entityCollection to visualize.
  544. */
  545. function PathVisualizer(scene, entityCollection) {
  546. //>>includeStart('debug', pragmas.debug);
  547. if (!defined(scene)) {
  548. throw new DeveloperError("scene is required.");
  549. }
  550. if (!defined(entityCollection)) {
  551. throw new DeveloperError("entityCollection is required.");
  552. }
  553. //>>includeEnd('debug');
  554. entityCollection.collectionChanged.addEventListener(
  555. PathVisualizer.prototype._onCollectionChanged,
  556. this
  557. );
  558. this._scene = scene;
  559. this._updaters = {};
  560. this._entityCollection = entityCollection;
  561. this._items = new AssociativeArray();
  562. this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
  563. }
  564. /**
  565. * Updates all of the primitives created by this visualizer to match their
  566. * Entity counterpart at the given time.
  567. *
  568. * @param {JulianDate} time The time to update to.
  569. * @returns {boolean} This function always returns true.
  570. */
  571. PathVisualizer.prototype.update = function (time) {
  572. //>>includeStart('debug', pragmas.debug);
  573. if (!defined(time)) {
  574. throw new DeveloperError("time is required.");
  575. }
  576. //>>includeEnd('debug');
  577. const updaters = this._updaters;
  578. for (const key in updaters) {
  579. if (updaters.hasOwnProperty(key)) {
  580. updaters[key].update(time);
  581. }
  582. }
  583. const items = this._items.values;
  584. if (
  585. items.length === 0 &&
  586. defined(this._updaters) &&
  587. Object.keys(this._updaters).length > 0
  588. ) {
  589. for (const u in updaters) {
  590. if (updaters.hasOwnProperty(u)) {
  591. updaters[u].destroy();
  592. }
  593. }
  594. this._updaters = {};
  595. }
  596. for (let i = 0, len = items.length; i < len; i++) {
  597. const item = items[i];
  598. const entity = item.entity;
  599. const positionProperty = entity._position;
  600. const lastUpdater = item.updater;
  601. let frameToVisualize = ReferenceFrame.FIXED;
  602. if (this._scene.mode === SceneMode.SCENE3D) {
  603. frameToVisualize = positionProperty.referenceFrame;
  604. }
  605. let currentUpdater = this._updaters[frameToVisualize];
  606. if (lastUpdater === currentUpdater && defined(currentUpdater)) {
  607. currentUpdater.updateObject(time, item);
  608. continue;
  609. }
  610. if (defined(lastUpdater)) {
  611. lastUpdater.removeObject(item);
  612. }
  613. if (!defined(currentUpdater)) {
  614. currentUpdater = new PolylineUpdater(this._scene, frameToVisualize);
  615. currentUpdater.update(time);
  616. this._updaters[frameToVisualize] = currentUpdater;
  617. }
  618. item.updater = currentUpdater;
  619. if (defined(currentUpdater)) {
  620. currentUpdater.updateObject(time, item);
  621. }
  622. }
  623. return true;
  624. };
  625. /**
  626. * Returns true if this object was destroyed; otherwise, false.
  627. *
  628. * @returns {boolean} True if this object was destroyed; otherwise, false.
  629. */
  630. PathVisualizer.prototype.isDestroyed = function () {
  631. return false;
  632. };
  633. /**
  634. * Removes and destroys all primitives created by this instance.
  635. */
  636. PathVisualizer.prototype.destroy = function () {
  637. this._entityCollection.collectionChanged.removeEventListener(
  638. PathVisualizer.prototype._onCollectionChanged,
  639. this
  640. );
  641. const updaters = this._updaters;
  642. for (const key in updaters) {
  643. if (updaters.hasOwnProperty(key)) {
  644. updaters[key].destroy();
  645. }
  646. }
  647. return destroyObject(this);
  648. };
  649. PathVisualizer.prototype._onCollectionChanged = function (
  650. entityCollection,
  651. added,
  652. removed,
  653. changed
  654. ) {
  655. let i;
  656. let entity;
  657. let item;
  658. const items = this._items;
  659. for (i = added.length - 1; i > -1; i--) {
  660. entity = added[i];
  661. if (defined(entity._path) && defined(entity._position)) {
  662. items.set(entity.id, new EntityData(entity));
  663. }
  664. }
  665. for (i = changed.length - 1; i > -1; i--) {
  666. entity = changed[i];
  667. if (defined(entity._path) && defined(entity._position)) {
  668. if (!items.contains(entity.id)) {
  669. items.set(entity.id, new EntityData(entity));
  670. }
  671. } else {
  672. item = items.get(entity.id);
  673. if (defined(item)) {
  674. if (defined(item.updater)) {
  675. item.updater.removeObject(item);
  676. }
  677. items.remove(entity.id);
  678. }
  679. }
  680. }
  681. for (i = removed.length - 1; i > -1; i--) {
  682. entity = removed[i];
  683. item = items.get(entity.id);
  684. if (defined(item)) {
  685. if (defined(item.updater)) {
  686. item.updater.removeObject(item);
  687. }
  688. items.remove(entity.id);
  689. }
  690. }
  691. };
  692. //for testing
  693. PathVisualizer._subSample = subSample;
  694. export default PathVisualizer;