bruce-cesium 5.9.6 → 5.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -72,5 +72,5 @@ __exportStar(require("./widgets/controls-view-bar/widget-control-view-bar-search
72
72
  __exportStar(require("./widgets/widget-left-panel"), exports);
73
73
  __exportStar(require("./widgets/tabs-left-panel/widget-left-panel-tab"), exports);
74
74
  __exportStar(require("./widgets/tabs-left-panel/widget-left-panel-tab-bookmarks"), exports);
75
- exports.VERSION = "5.9.6";
75
+ exports.VERSION = "5.9.8";
76
76
  //# sourceMappingURL=bruce-cesium.js.map
@@ -372,19 +372,18 @@ var CesiumAnimatedProperty;
372
372
  */
373
373
  class AnimatePositionSeries {
374
374
  constructor(params) {
375
- // Animation state.
376
- this.currentAnimatedPos = null;
377
- this.currentTargetPos = null;
378
- this.animationStartPos = null;
379
- this.animationStartTime = null;
380
- this.animationDuration = 200;
381
- // Cached data for performance.
375
+ this.currentPos = null;
376
+ this.currentVelocity = new Cesium.Cartesian3(0, 0, 0);
377
+ this.smoothingFactor = 0.5;
378
+ this.positionHistory = [];
379
+ this.maxHistorySize = 10;
380
+ this.averageSpeed = 0;
381
+ this.averageTimeInterval = 0;
382
+ this.lastUpdateTime = null;
382
383
  this.lastDesiredPosIndex = -1;
383
384
  this.lastDesiredNextIndex = -1;
384
- // Series data for rendering path
385
385
  this.lastCalcSeriesPos3d = [];
386
386
  this.lastCalcSeriesTime = null;
387
- // Orientation cache.
388
387
  this.lastCalcOrient = null;
389
388
  this.lastCalcOrientTime = null;
390
389
  this.viewer = params.viewer;
@@ -393,25 +392,23 @@ var CesiumAnimatedProperty;
393
392
  this.roll = params.roll || 0;
394
393
  this.processHeadings();
395
394
  this.sortPositions();
396
- // Initialize animation from starting position if provided.
397
- if (params.animateFromPos3d) {
398
- this.animationStartPos = params.animateFromPos3d;
399
- const currentTime = Date.now();
400
- const providedTime = params.animateFromPos3dTimeStart || 0;
401
- // Check if the provided timestamp is stale.
402
- if (!providedTime || (currentTime - providedTime) >= this.animationDuration) {
403
- this.animationStartTime = currentTime;
404
- }
405
- else {
406
- this.animationStartTime = providedTime;
407
- }
408
- }
409
395
  }
410
396
  AddPosition(pos) {
411
397
  if (!pos || !pos.pos3d || !pos.dateTime) {
412
398
  console.warn("Invalid position provided to AnimatePositionSeries.");
413
399
  return;
414
400
  }
401
+ const now = Date.now();
402
+ const posTime = pos.dateTime.getTime();
403
+ this.positionHistory.push({
404
+ pos: pos.pos3d.clone(),
405
+ time: posTime,
406
+ realTime: now
407
+ });
408
+ if (this.positionHistory.length > this.maxHistorySize) {
409
+ this.positionHistory.shift();
410
+ }
411
+ this.analyzeMovementPatterns();
415
412
  this.positions.push(pos);
416
413
  this.processHeadings();
417
414
  this.sortPositions();
@@ -422,12 +419,70 @@ var CesiumAnimatedProperty;
422
419
  this.roll = roll;
423
420
  this.invalidateCache();
424
421
  }
425
- GetAnimateFromDateTime() {
426
- return this.animationStartTime;
427
- }
428
422
  GetPositions() {
429
423
  return this.positions;
430
424
  }
425
+ analyzeMovementPatterns() {
426
+ if (this.positionHistory.length < 2) {
427
+ return;
428
+ }
429
+ let totalDistance = 0;
430
+ let totalTimeSpan = 0;
431
+ let totalRealTimeSpan = 0;
432
+ let validSegments = 0;
433
+ for (let i = 1; i < this.positionHistory.length; i++) {
434
+ const prev = this.positionHistory[i - 1];
435
+ const curr = this.positionHistory[i];
436
+ const distance = Cesium.Cartesian3.distance(prev.pos, curr.pos);
437
+ const timeSpan = Math.abs(curr.time - prev.time) / 1000.0;
438
+ const realTimeSpan = Math.abs(curr.realTime - prev.realTime) / 1000.0;
439
+ if (distance > 0.1 && timeSpan > 0.01) {
440
+ totalDistance += distance;
441
+ totalTimeSpan += timeSpan;
442
+ totalRealTimeSpan += realTimeSpan;
443
+ validSegments++;
444
+ }
445
+ }
446
+ if (validSegments > 0) {
447
+ this.averageSpeed = totalDistance / totalTimeSpan;
448
+ this.averageTimeInterval = totalRealTimeSpan / validSegments;
449
+ this.averageSpeed = Math.max(0.1, Math.min(this.averageSpeed, 1000));
450
+ this.averageTimeInterval = Math.max(0.01, Math.min(this.averageTimeInterval, 10));
451
+ }
452
+ }
453
+ calculateExpectedPosition(viewerTimeMs) {
454
+ const desired = this.calculateDesiredPosition(viewerTimeMs);
455
+ if (!desired.position) {
456
+ return { position: null, timeDelta: 0 };
457
+ }
458
+ let timeDelta = 0;
459
+ if (this.positionHistory.length >= 2) {
460
+ // Calculate how far behind we are based on viewer time vs real time.
461
+ const now = Date.now();
462
+ const timeDiffFromNow = (viewerTimeMs - now) / 1000.0;
463
+ // Negative means we're behind, positive means ahead.
464
+ timeDelta = timeDiffFromNow;
465
+ }
466
+ return { position: desired.position, timeDelta };
467
+ }
468
+ calculateAutonomousSpeed(distanceToTarget, timeDelta) {
469
+ let baseSpeed = this.averageSpeed || 50;
470
+ let speedMultiplier = 1.0;
471
+ // Calculate speed multiplier based on distance and time lag.
472
+ if (distanceToTarget > 50) {
473
+ speedMultiplier *= Math.min(100.0, distanceToTarget / 10.0);
474
+ }
475
+ else if (distanceToTarget > 10) {
476
+ const distanceMultiplier = Math.min(20.0, 1.0 + (distanceToTarget / 5.0));
477
+ speedMultiplier *= distanceMultiplier;
478
+ }
479
+ else if (Math.abs(timeDelta) > 0.1) {
480
+ const timeLagMultiplier = Math.max(0.1, 1.0 - (timeDelta * 5.0));
481
+ speedMultiplier *= Math.min(50.0, timeLagMultiplier);
482
+ }
483
+ // Minimum 10% speed.
484
+ return Math.max(baseSpeed * 0.1, baseSpeed * speedMultiplier);
485
+ }
431
486
  sortPositions() {
432
487
  if (this.positions.length > 0) {
433
488
  this.positions.sort((a, b) => a.dateTime.getTime() - b.dateTime.getTime());
@@ -437,10 +492,6 @@ var CesiumAnimatedProperty;
437
492
  this.lastCalcSeriesTime = null;
438
493
  this.lastCalcOrientTime = null;
439
494
  }
440
- /**
441
- * Pre-process headings in the series.
442
- * If all are null or 0, then we assume all are null.
443
- */
444
495
  processHeadings() {
445
496
  if (!this.positions || this.positions.length === 0) {
446
497
  return;
@@ -462,15 +513,10 @@ var CesiumAnimatedProperty;
462
513
  easeInOutQuad(t) {
463
514
  return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
464
515
  }
465
- /**
466
- * Calculate the desired position based on current time without any animation smoothing.
467
- */
468
516
  calculateDesiredPosition(timeMs) {
469
- // No positions available
470
517
  if (!this.positions || this.positions.length === 0) {
471
518
  return { position: null, lastIndex: -1, nextIndex: -1 };
472
519
  }
473
- // Only one position.
474
520
  if (this.positions.length === 1) {
475
521
  return {
476
522
  position: this.positions[0].pos3d || null,
@@ -478,7 +524,6 @@ var CesiumAnimatedProperty;
478
524
  nextIndex: 0
479
525
  };
480
526
  }
481
- // Before first position - use first two positions for orientation.
482
527
  if (timeMs <= this.positions[0].dateTime.getTime()) {
483
528
  return {
484
529
  position: this.positions[0].pos3d || null,
@@ -486,7 +531,6 @@ var CesiumAnimatedProperty;
486
531
  nextIndex: Math.min(1, this.positions.length - 1)
487
532
  };
488
533
  }
489
- // After last position - use last two positions for orientation.
490
534
  const lastIdx = this.positions.length - 1;
491
535
  if (timeMs >= this.positions[lastIdx].dateTime.getTime()) {
492
536
  return {
@@ -495,12 +539,10 @@ var CesiumAnimatedProperty;
495
539
  nextIndex: lastIdx
496
540
  };
497
541
  }
498
- // Find positions to interpolate between.
499
542
  for (let i = 0; i < this.positions.length - 1; i++) {
500
543
  const current = this.positions[i];
501
544
  const next = this.positions[i + 1];
502
545
  if (timeMs >= current.dateTime.getTime() && timeMs < next.dateTime.getTime()) {
503
- // Exact match on current position - still use current and next for orientation.
504
546
  if (timeMs === current.dateTime.getTime()) {
505
547
  return {
506
548
  position: current.pos3d || null,
@@ -508,7 +550,6 @@ var CesiumAnimatedProperty;
508
550
  nextIndex: i + 1
509
551
  };
510
552
  }
511
- // Interpolate between current and next.
512
553
  if (!current.pos3d || !next.pos3d) {
513
554
  return {
514
555
  position: current.pos3d || next.pos3d || null,
@@ -535,99 +576,59 @@ var CesiumAnimatedProperty;
535
576
  }
536
577
  }
537
578
  }
538
- // Fallback to last position with previous position for orientation.
539
579
  return {
540
580
  position: this.positions[lastIdx].pos3d || null,
541
581
  lastIndex: Math.max(0, lastIdx - 1),
542
582
  nextIndex: lastIdx
543
583
  };
544
584
  }
545
- /**
546
- * Main method to get the current position based on time.
547
- */
548
585
  GetValue() {
549
- let now = this.viewer.scene.lastRenderTime;
550
- if (!now) {
551
- now = this.viewer.clock.currentTime;
586
+ let viewerTime = this.viewer.scene.lastRenderTime;
587
+ if (!viewerTime) {
588
+ viewerTime = this.viewer.clock.currentTime;
552
589
  }
553
- const nowTimeMs = Cesium.JulianDate.toDate(now).getTime();
590
+ const viewerTimeMs = Cesium.JulianDate.toDate(viewerTime).getTime();
554
591
  const currentRealTimeMs = Date.now();
555
- // Calculate the desired position based on time.
556
- const desired = this.calculateDesiredPosition(nowTimeMs);
557
- // If no desired position, return empty position.
558
- if (!desired.position) {
559
- this.currentAnimatedPos = null;
560
- this.currentTargetPos = null;
592
+ const expected = this.calculateExpectedPosition(viewerTimeMs);
593
+ if (!expected.position) {
594
+ this.currentPos = null;
595
+ this.currentVelocity = new Cesium.Cartesian3(0, 0, 0);
561
596
  return new Cesium.Cartesian3();
562
597
  }
563
- // Cache the desired position info for orientation calculations.
598
+ const desired = this.calculateDesiredPosition(viewerTimeMs);
564
599
  this.lastDesiredPosIndex = desired.lastIndex;
565
600
  this.lastDesiredNextIndex = desired.nextIndex;
566
- // First time or no previous position - start here.
567
- if (!this.currentAnimatedPos) {
568
- // If we have a starting animation position, animate from it.
569
- if (this.animationStartPos) {
570
- this.currentTargetPos = desired.position;
571
- const progress = this.getAnimationProgress(currentRealTimeMs, this.animationStartTime);
572
- if (progress >= 1.0) {
573
- // Animation complete.
574
- this.currentAnimatedPos = desired.position;
575
- this.animationStartPos = null;
576
- this.animationStartTime = null;
577
- return desired.position;
578
- }
579
- else {
580
- // Continue animation.
581
- const easedProgress = this.easeInOutQuad(progress);
582
- this.currentAnimatedPos = Cesium.Cartesian3.lerp(this.animationStartPos, desired.position, easedProgress, new Cesium.Cartesian3());
583
- return this.currentAnimatedPos;
584
- }
585
- }
586
- else {
587
- // No animation, start at desired position.
588
- this.currentAnimatedPos = desired.position;
589
- this.currentTargetPos = desired.position;
590
- return desired.position;
591
- }
592
- }
593
- // Check if target has changed.
594
- const targetChanged = !this.currentTargetPos || !Cesium.Cartesian3.equals(this.currentTargetPos, desired.position);
595
- if (targetChanged) {
596
- // Target changed mid-animation - start new animation from current position.
597
- this.animationStartPos = this.currentAnimatedPos;
598
- this.animationStartTime = currentRealTimeMs;
599
- this.currentTargetPos = desired.position;
600
- }
601
- // Continue or start animation to target.
602
- if (this.animationStartPos && this.animationStartTime) {
603
- const progress = this.getAnimationProgress(currentRealTimeMs, this.animationStartTime);
604
- // Animation complete.
605
- if (progress >= 1.0) {
606
- this.currentAnimatedPos = desired.position;
607
- this.animationStartPos = null;
608
- this.animationStartTime = null;
609
- return desired.position;
610
- }
611
- // Continue animation.
612
- else {
613
- const easedProgress = this.easeInOutQuad(progress);
614
- this.currentAnimatedPos = Cesium.Cartesian3.lerp(this.animationStartPos, desired.position, easedProgress, new Cesium.Cartesian3());
615
- return this.currentAnimatedPos;
616
- }
617
- }
618
- // No animation needed - already at target.
619
- this.currentAnimatedPos = desired.position;
620
- return desired.position;
621
- }
622
- getAnimationProgress(currentTime, startTime) {
623
- const elapsed = currentTime - startTime;
624
- return Math.min(elapsed / this.animationDuration, 1.0);
601
+ if (!this.currentPos) {
602
+ this.currentPos = expected.position.clone();
603
+ this.currentVelocity = new Cesium.Cartesian3(0, 0, 0);
604
+ this.lastUpdateTime = currentRealTimeMs;
605
+ return this.currentPos;
606
+ }
607
+ const deltaTime = this.lastUpdateTime ? (currentRealTimeMs - this.lastUpdateTime) / 1000.0 : 0.016;
608
+ this.lastUpdateTime = currentRealTimeMs;
609
+ const clampedDeltaTime = Math.min(deltaTime, 0.1);
610
+ const targetDistance = Cesium.Cartesian3.distance(this.currentPos, expected.position);
611
+ if (targetDistance < 0.5) {
612
+ this.currentPos = expected.position.clone();
613
+ this.currentVelocity = new Cesium.Cartesian3(0, 0, 0);
614
+ return this.currentPos;
615
+ }
616
+ // Check if we're really far behind and need to teleport.
617
+ if (targetDistance > 500) {
618
+ this.currentPos = expected.position.clone();
619
+ this.currentVelocity = new Cesium.Cartesian3(0, 0, 0);
620
+ return this.currentPos;
621
+ }
622
+ const direction = Cesium.Cartesian3.subtract(expected.position, this.currentPos, new Cesium.Cartesian3());
623
+ Cesium.Cartesian3.normalize(direction, direction);
624
+ const targetSpeed = this.calculateAutonomousSpeed(targetDistance, expected.timeDelta);
625
+ const targetVelocity = Cesium.Cartesian3.multiplyByScalar(direction, targetSpeed, new Cesium.Cartesian3());
626
+ this.currentVelocity = Cesium.Cartesian3.lerp(this.currentVelocity, targetVelocity, this.smoothingFactor, new Cesium.Cartesian3());
627
+ const velocityDelta = Cesium.Cartesian3.multiplyByScalar(this.currentVelocity, clampedDeltaTime, new Cesium.Cartesian3());
628
+ this.currentPos = Cesium.Cartesian3.add(this.currentPos, velocityDelta, new Cesium.Cartesian3());
629
+ return this.currentPos;
625
630
  }
626
- /**
627
- * Returns a series of positions to use for rendering the path.
628
- */
629
631
  GetSeries() {
630
- // Update at 30 fps.
631
632
  let doUpdate = this.lastCalcSeriesTime == null;
632
633
  if (!doUpdate && this.lastCalcSeriesTime && (new Date().getTime() - this.lastCalcSeriesTime) > 1000 / 30) {
633
634
  doUpdate = true;
@@ -635,33 +636,27 @@ var CesiumAnimatedProperty;
635
636
  if (!doUpdate) {
636
637
  return this.lastCalcSeriesPos3d;
637
638
  }
638
- // Ensure position indices are up-to-date.
639
639
  this.GetValue();
640
640
  let now = this.viewer.scene.lastRenderTime;
641
641
  if (!now) {
642
642
  now = this.viewer.clock.currentTime;
643
643
  }
644
644
  const nowDate = Cesium.JulianDate.toDate(now);
645
- // Get total duration.
646
645
  if (!this.positions || this.positions.length < 2) {
647
646
  this.lastCalcSeriesTime = nowDate.getTime();
648
647
  this.lastCalcSeriesPos3d = [];
649
648
  return [];
650
649
  }
651
- const totalDuration = this.positions[this.positions.length - 1].dateTime.getTime() -
652
- this.positions[0].dateTime.getTime();
653
- // Percentage of the polyline to be visible before and after each point.
654
- const visibilityPercentage = 0.05; // 5%
650
+ const totalDuration = this.positions[this.positions.length - 1].dateTime.getTime() - this.positions[0].dateTime.getTime();
651
+ const visibilityPercentage = 0.05;
655
652
  const visibilityDuration = totalDuration * visibilityPercentage;
656
- // Gather positions that fall within the visibility duration.
657
653
  const newPosses = [];
658
654
  for (let i = 0; i < this.positions.length; i++) {
659
655
  const pos = this.positions[i];
660
- if (!pos.pos3d)
656
+ if (!pos.pos3d) {
661
657
  continue;
662
- let add = nowDate >= new Date(pos.dateTime.getTime() - visibilityDuration / 2) &&
663
- nowDate <= new Date(pos.dateTime.getTime() + visibilityDuration / 2);
664
- // Also include the segment we're currently traversing.
658
+ }
659
+ let add = nowDate >= new Date(pos.dateTime.getTime() - visibilityDuration / 2) && nowDate <= new Date(pos.dateTime.getTime() + visibilityDuration / 2);
665
660
  if (!add && this.lastDesiredPosIndex > -1 && this.lastDesiredNextIndex > -1) {
666
661
  add = i >= this.lastDesiredPosIndex && i <= this.lastDesiredNextIndex;
667
662
  }
@@ -673,11 +668,7 @@ var CesiumAnimatedProperty;
673
668
  this.lastCalcSeriesPos3d = newPosses;
674
669
  return newPosses;
675
670
  }
676
- /**
677
- * Returns the orientation based on current position and heading.
678
- */
679
671
  GetOrient() {
680
- // Update at 30 fps.
681
672
  let doUpdate = this.lastCalcOrientTime == null;
682
673
  if (!doUpdate && this.lastCalcOrientTime && (new Date().getTime() - this.lastCalcOrientTime) > 1000 / 30) {
683
674
  doUpdate = true;
@@ -685,26 +676,34 @@ var CesiumAnimatedProperty;
685
676
  if (!doUpdate && this.lastCalcOrient) {
686
677
  return this.lastCalcOrient;
687
678
  }
688
- // Default quaternion to return if we can't calculate.
689
679
  const defaultQuaternion = new Cesium.Quaternion();
690
680
  if (!this.positions || this.positions.length === 0) {
691
681
  return defaultQuaternion;
692
682
  }
693
- // Ensure position is up-to-date.
694
683
  const currentPosition = this.GetValue();
695
684
  if (!currentPosition) {
696
685
  return defaultQuaternion;
697
686
  }
698
- let now = this.viewer.scene.lastRenderTime;
699
- if (!now) {
700
- now = this.viewer.clock.currentTime;
701
- }
702
- const nowTime = Cesium.JulianDate.toDate(now).getTime();
703
687
  try {
704
- // Get current position indices.
688
+ const velocityMagnitude = Cesium.Cartesian3.magnitude(this.currentVelocity);
689
+ const minimumSpeedForVelocityOrientation = Math.max(1.0, this.averageSpeed * 0.1);
690
+ if (velocityMagnitude > minimumSpeedForVelocityOrientation) {
691
+ const normalizedVelocity = Cesium.Cartesian3.normalize(this.currentVelocity, new Cesium.Cartesian3());
692
+ const rotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(currentPosition, normalizedVelocity);
693
+ const quaternion = Cesium.Quaternion.fromRotationMatrix(rotationMatrix);
694
+ const hpr = new Cesium.HeadingPitchRoll(0, Cesium.Math.toRadians(this.pitch), Cesium.Math.toRadians(this.roll));
695
+ const pitchRollQuaternion = Cesium.Quaternion.fromHeadingPitchRoll(hpr);
696
+ this.lastCalcOrient = Cesium.Quaternion.multiply(quaternion, pitchRollQuaternion, new Cesium.Quaternion());
697
+ this.lastCalcOrientTime = Date.now();
698
+ return this.lastCalcOrient;
699
+ }
700
+ let now = this.viewer.scene.lastRenderTime;
701
+ if (!now) {
702
+ now = this.viewer.clock.currentTime;
703
+ }
704
+ const nowTime = Cesium.JulianDate.toDate(now).getTime();
705
705
  const lastIndex = this.lastDesiredPosIndex;
706
706
  const nextIndex = this.lastDesiredNextIndex;
707
- // Invalid indices.
708
707
  if (lastIndex < 0 || nextIndex < 0 ||
709
708
  lastIndex >= this.positions.length || nextIndex >= this.positions.length) {
710
709
  return this.lastCalcOrient || defaultQuaternion;
@@ -714,74 +713,53 @@ var CesiumAnimatedProperty;
714
713
  if (!lastPos || !nextPos) {
715
714
  return this.lastCalcOrient || defaultQuaternion;
716
715
  }
717
- // Single position case - use its heading if available.
718
716
  if (lastIndex === nextIndex) {
719
717
  if (lastPos.heading !== null) {
720
718
  this.lastCalcOrient = Cesium.Transforms.headingPitchRollQuaternion(currentPosition, new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(lastPos.heading), Cesium.Math.toRadians(this.pitch), Cesium.Math.toRadians(this.roll)));
721
719
  this.lastCalcOrientTime = Date.now();
722
720
  return this.lastCalcOrient;
723
721
  }
724
- // No heading data and single position, return previous or default.
725
722
  return this.lastCalcOrient || defaultQuaternion;
726
723
  }
727
- // Two different positions - we can calculate orientation.
728
- // Use explicit heading values if available.
729
724
  if (lastPos.heading !== null && nextPos.heading !== null) {
730
- // Calculate interpolated heading.
731
725
  let deltaHeading = nextPos.heading - lastPos.heading;
732
- // Handle wrap-around between 359° and 0°.
733
726
  if (deltaHeading > 180) {
734
727
  deltaHeading -= 360;
735
728
  }
736
729
  else if (deltaHeading < -180) {
737
730
  deltaHeading += 360;
738
731
  }
739
- // Calculate interpolation factor.
740
732
  let factor = 0;
741
733
  if (lastPos.dateTime.getTime() !== nextPos.dateTime.getTime()) {
742
- factor = (nowTime - lastPos.dateTime.getTime()) /
743
- (nextPos.dateTime.getTime() - lastPos.dateTime.getTime());
734
+ factor = (nowTime - lastPos.dateTime.getTime()) / (nextPos.dateTime.getTime() - lastPos.dateTime.getTime());
744
735
  factor = Math.max(0, Math.min(1, factor));
745
736
  }
746
- // Apply easing for smoother rotation.
747
737
  factor = this.easeInOutQuad(factor);
748
- // Calculate final heading.
749
738
  let interpolatedHeading = lastPos.heading + factor * deltaHeading;
750
739
  interpolatedHeading = (interpolatedHeading + 360) % 360;
751
- // Create quaternion from heading, pitch, roll.
752
740
  this.lastCalcOrient = Cesium.Transforms.headingPitchRollQuaternion(currentPosition, new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(interpolatedHeading), Cesium.Math.toRadians(this.pitch), Cesium.Math.toRadians(this.roll)));
753
741
  }
754
- // Calculate heading from position changes if heading data not available.
755
742
  else {
756
743
  if (!lastPos.pos3d || !nextPos.pos3d) {
757
744
  return this.lastCalcOrient || defaultQuaternion;
758
745
  }
759
- // Flatten positions to avoid altitude-related heading changes.
760
746
  const adjustedPointPrev = Cesium.Cartographic.fromCartesian(lastPos.pos3d);
761
747
  const adjustedPos3dPrev = Cesium.Cartesian3.fromRadians(adjustedPointPrev.longitude, adjustedPointPrev.latitude, 0);
762
748
  const adjustedPointNext = Cesium.Cartographic.fromCartesian(nextPos.pos3d);
763
749
  const adjustedPos3dNext = Cesium.Cartesian3.fromRadians(adjustedPointNext.longitude, adjustedPointNext.latitude, 0);
764
- // Skip if positions are too close (less than 5cm).
765
750
  const distance = Cesium.Cartesian3.distance(adjustedPos3dPrev, adjustedPos3dNext);
766
751
  if (distance < 0.05) {
767
752
  return this.lastCalcOrient || defaultQuaternion;
768
753
  }
769
- // Calculate direction vector.
770
754
  const direction = Cesium.Cartesian3.subtract(adjustedPos3dNext, adjustedPos3dPrev, new Cesium.Cartesian3());
771
- // Skip if no movement.
772
755
  if (direction.x === 0 && direction.y === 0 && direction.z === 0) {
773
756
  return this.lastCalcOrient || defaultQuaternion;
774
757
  }
775
- // Normalize the direction vector.
776
758
  Cesium.Cartesian3.normalize(direction, direction);
777
- // Calculate rotation based on movement direction.
778
759
  const rotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(currentPosition, direction);
779
- // Convert to quaternion.
780
760
  const quaternion = Cesium.Quaternion.fromRotationMatrix(rotationMatrix);
781
- // Add pitch and roll adjustments.
782
761
  const hpr = new Cesium.HeadingPitchRoll(0, Cesium.Math.toRadians(this.pitch), Cesium.Math.toRadians(this.roll));
783
762
  const pitchRollQuaternion = Cesium.Quaternion.fromHeadingPitchRoll(hpr);
784
- // Combine quaternions.
785
763
  this.lastCalcOrient = Cesium.Quaternion.multiply(quaternion, pitchRollQuaternion, new Cesium.Quaternion());
786
764
  }
787
765
  }
@@ -792,6 +770,52 @@ var CesiumAnimatedProperty;
792
770
  this.lastCalcOrientTime = Date.now();
793
771
  return this.lastCalcOrient;
794
772
  }
773
+ GetCurrentVelocity() {
774
+ return this.currentVelocity ? this.currentVelocity.clone() : new Cesium.Cartesian3(0, 0, 0);
775
+ }
776
+ SupplementSeries(newSeries) {
777
+ if (!newSeries || newSeries.length === 0) {
778
+ return;
779
+ }
780
+ for (const pos of newSeries) {
781
+ if (!pos || !pos.pos3d || !pos.dateTime) {
782
+ continue;
783
+ }
784
+ const existingIndex = this.positions.findIndex(p => p.dateTime.getTime() === pos.dateTime.getTime());
785
+ if (existingIndex >= 0) {
786
+ this.positions[existingIndex] = pos;
787
+ }
788
+ else {
789
+ this.AddPosition(pos);
790
+ }
791
+ }
792
+ }
793
+ UpdatePositionForDateTime(pos3d, dateTime, heading) {
794
+ if (!pos3d || !dateTime) {
795
+ return;
796
+ }
797
+ const existingIndex = this.positions.findIndex(p => p.dateTime.getTime() === dateTime.getTime());
798
+ const newPos = {
799
+ pos3d: pos3d,
800
+ dateTime: dateTime,
801
+ heading: heading !== undefined ? heading : null
802
+ };
803
+ if (existingIndex >= 0) {
804
+ this.positions[existingIndex] = newPos;
805
+ this.processHeadings();
806
+ this.sortPositions();
807
+ this.invalidateCache();
808
+ }
809
+ else {
810
+ this.AddPosition(newPos);
811
+ }
812
+ }
813
+ HasPositionForDateTime(dateTime) {
814
+ return this.positions.some(p => p.dateTime.getTime() === dateTime.getTime());
815
+ }
816
+ GetPositionCount() {
817
+ return this.positions.length;
818
+ }
795
819
  }
796
820
  CesiumAnimatedProperty.AnimatePositionSeries = AnimatePositionSeries;
797
821
  function GetSeriesPossesForHistoricEntity(viewer, dataHeightRef, heightRef, historic) {