bruce-cesium 7.4.1 → 7.4.3

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.
@@ -5383,7 +5383,14 @@
5383
5383
  (function (EntityLabel) {
5384
5384
  const ICON_BACKGROUND_BLEND = 0.8;
5385
5385
  const ICON_BACKGROUND_ALPHA = 0.45;
5386
+ // Under this much difference between the glyph's width and height the chip is squared off,
5387
+ // so a row of labels reads as one size rather than a set of differently shaped pills.
5388
+ const ICON_BACKGROUND_UNIFORM_TOLERANCE = 0.3;
5386
5389
  const TEXT_MAX_WIDTH = 200;
5390
+ // How far an angled leader runs sideways before the label starts.
5391
+ const ANGLED_LEADER_RUN = 28;
5392
+ // One per tinted icon, since an SVG mask is addressed by id across the whole document.
5393
+ let tintCounter = 0;
5387
5394
  // Key where the label instance is stored within the cesium entity.
5388
5395
  // Eg: rego[ATTACH_KEY] = new EntityLabel(...);
5389
5396
  EntityLabel.ATTACH_KEY = "nextspace-navigator-entity-label";
@@ -5482,7 +5489,7 @@
5482
5489
  this.nextOrder = 0;
5483
5490
  }
5484
5491
  Resolve(frames) {
5485
- var _a, _b;
5492
+ var _a, _b, _c;
5486
5493
  const live = new Set(frames.map(frame => frame.id));
5487
5494
  const moved = (frame) => {
5488
5495
  var _a;
@@ -5509,10 +5516,17 @@
5509
5516
  const placed = new Map();
5510
5517
  const next = new Map();
5511
5518
  const candidate = (frame, side) => {
5512
- var _a;
5513
- return (side === "left" && frame.canFlip ?
5514
- { ...frame, x: Math.max(0, Math.min(frame.anchorX - frame.width, ((_a = frame.viewportWidth) !== null && _a !== void 0 ? _a : Infinity) - frame.width)) } :
5515
- frame);
5519
+ var _a, _b;
5520
+ if (!frame.canFlip) {
5521
+ return frame;
5522
+ }
5523
+ if (frame.leaderRun != null) {
5524
+ const x = side === "left" ? frame.anchorX - frame.leaderRun - frame.width : frame.anchorX + frame.leaderRun;
5525
+ return { ...frame, x: Math.max(0, Math.min(x, ((_a = frame.viewportWidth) !== null && _a !== void 0 ? _a : Infinity) - frame.width)) };
5526
+ }
5527
+ return (side === "left" ?
5528
+ { ...frame, x: Math.max(0, Math.min(frame.anchorX - frame.width, ((_b = frame.viewportWidth) !== null && _b !== void 0 ? _b : Infinity) - frame.width)) }
5529
+ : frame);
5516
5530
  };
5517
5531
  const score = (frame, padding = 0) => {
5518
5532
  const box = {
@@ -5528,7 +5542,7 @@
5528
5542
  };
5529
5543
  for (const frame of ordered) {
5530
5544
  const old = this.previous.get(frame.id);
5531
- let side = frame.canFlip ? (_a = old === null || old === void 0 ? void 0 : old.side) !== null && _a !== void 0 ? _a : "right" : "right";
5545
+ let side = frame.canFlip ? (_b = (_a = old === null || old === void 0 ? void 0 : old.side) !== null && _a !== void 0 ? _a : frame.leaderSide) !== null && _b !== void 0 ? _b : "right" : "right";
5532
5546
  let chosen = candidate(frame, side);
5533
5547
  if (!fixed(frame) && frame.canFlip) {
5534
5548
  const otherSide = side === "left" ? "right" : "left";
@@ -5540,7 +5554,7 @@
5540
5554
  }
5541
5555
  }
5542
5556
  placed.set(frame.id, { frame: chosen, side });
5543
- next.set(frame.id, { frame: fixed(frame) ? old.frame : { ...frame }, side, order: (_b = old === null || old === void 0 ? void 0 : old.order) !== null && _b !== void 0 ? _b : this.nextOrder++ });
5557
+ next.set(frame.id, { frame: fixed(frame) ? old.frame : { ...frame }, side, order: (_c = old === null || old === void 0 ? void 0 : old.order) !== null && _c !== void 0 ? _c : this.nextOrder++ });
5544
5558
  text.Add(chosen);
5545
5559
  const stem = leaderBounds(chosen);
5546
5560
  if (stem) {
@@ -5560,7 +5574,7 @@
5560
5574
  * Resolves clustering and stable text placement before depth fading; highlighted labels receive priority.
5561
5575
  */
5562
5576
  function Layout(input, memory = new PlacementMemory()) {
5563
- var _a, _b, _c, _d, _e, _f, _g, _h;
5577
+ var _a, _b, _c, _d, _e, _f, _g;
5564
5578
  const frames = input.filter(frame => [frame.x, frame.y, frame.width, frame.height, frame.depth].every(Number.isFinite))
5565
5579
  .sort((a, b) => (b.priority || 0) - (a.priority || 0) || a.depth - b.depth || a.id.localeCompare(b.id));
5566
5580
  const result = new Map();
@@ -5626,17 +5640,13 @@
5626
5640
  }
5627
5641
  }
5628
5642
  if (values.size > 1) {
5629
- const description = stableMembers.find(member => { var _a, _b; return member.textWrapped && ((_b = (member.text || ((_a = member.options) === null || _a === void 0 ? void 0 : _a.text))) === null || _b === void 0 ? void 0 : _b.trim()); });
5630
- if (description) {
5631
- representative.text = ((_f = description.text) !== null && _f !== void 0 ? _f : description.options.text).trim();
5632
- representative.summaryMore = true;
5633
- }
5634
- else {
5635
- const rows = Array.from(values).slice(0, 2);
5636
- if (values.size > 2)
5637
- rows.push(`+${values.size - 2} more…`);
5638
- representative.text = rows.join("\n");
5639
- }
5643
+ // A value long enough to wrap fills the box on its own, so only one is listed.
5644
+ const wrapped = stableMembers.some(member => { var _a, _b; return member.textWrapped && ((_b = (member.text || ((_a = member.options) === null || _a === void 0 ? void 0 : _a.text))) === null || _b === void 0 ? void 0 : _b.trim()); });
5645
+ const limit = wrapped ? 1 : 2;
5646
+ const rows = Array.from(values).slice(0, limit);
5647
+ if (values.size > limit)
5648
+ rows.push(`+${values.size - limit} more…`);
5649
+ representative.text = rows.join("\n");
5640
5650
  }
5641
5651
  }
5642
5652
  for (const frame of cluster.members) {
@@ -5660,7 +5670,7 @@
5660
5670
  const box = { ...frame, x: frame.x + .01, y: frame.y + .01, width: Math.max(0, frame.width - .02), height: Math.max(0, frame.height - .02) };
5661
5671
  const overlap = foreground.HasOverlap(box) || foregroundLeaders.HasOverlap(box) || Boolean(stem && foreground.HasOverlap(stem));
5662
5672
  if (overlap) {
5663
- placement.opacity *= Math.max(0, Math.min(1, (_h = (_g = frame.options) === null || _g === void 0 ? void 0 : _g.overlapOpacity) !== null && _h !== void 0 ? _h : 0.25));
5673
+ placement.opacity *= Math.max(0, Math.min(1, (_g = (_f = frame.options) === null || _f === void 0 ? void 0 : _f.overlapOpacity) !== null && _g !== void 0 ? _g : 0.25));
5664
5674
  }
5665
5675
  placement.zIndex = zIndex--;
5666
5676
  foreground.Add(frame);
@@ -5982,6 +5992,8 @@
5982
5992
  this.loadTimeout = null;
5983
5993
  this.loadedName = false;
5984
5994
  this.line = null;
5995
+ // The sideways half of an angled leader; the rise is drawn by line.
5996
+ this.leaderRun = null;
5985
5997
  this.anchor = null;
5986
5998
  // Null = not attempted to load.
5987
5999
  this.additionalTexts = null;
@@ -6378,6 +6390,7 @@
6378
6390
  }
6379
6391
  };
6380
6392
  const normalLeader = (_k = this.options.leader) !== null && _k !== void 0 ? _k : presentation === "text";
6393
+ const angled = this.options.leaderStyle === "angled";
6381
6394
  let leader = normalLeader;
6382
6395
  const content = document.createElement("span");
6383
6396
  const originalText = (_l = this.options.text) !== null && _l !== void 0 ? _l : [text, ...additionalTexts].join("\n");
@@ -6386,6 +6399,7 @@
6386
6399
  let sourceNaturalText;
6387
6400
  let iconElement;
6388
6401
  let iconContainer;
6402
+ let fitIconBackground;
6389
6403
  content.className = "entity-label-content";
6390
6404
  content.style.cssText = "display:flex;align-items:center;gap:6px;";
6391
6405
  if (presentation === "icon-text-above") {
@@ -6408,34 +6422,49 @@
6408
6422
  image.alt = this.options.icon || "";
6409
6423
  const scale = (_o = this.options.iconScale) !== null && _o !== void 0 ? _o : 1;
6410
6424
  const size = this.options.iconSize;
6411
- const mask = iconColor && iconBackground ? document.createElement("span") : icon;
6412
- if (mask !== icon) {
6413
- icon.appendChild(mask);
6414
- }
6415
- const target = iconColor ? mask : image;
6416
- if (iconColor) {
6417
- mask.style.display = "inline-block";
6418
- mask.style.backgroundColor = iconColor;
6419
- for (const prefix of ["", "-webkit-"]) {
6420
- mask.style.setProperty(`${prefix}mask-image`, `url(${JSON.stringify(this.options.iconUrl)})`);
6421
- mask.style.setProperty(`${prefix}mask-size`, "contain");
6422
- mask.style.setProperty(`${prefix}mask-repeat`, "no-repeat");
6423
- mask.style.setProperty(`${prefix}mask-position`, "center");
6424
- }
6425
+ const svgNs = "http://www.w3.org/2000/svg";
6426
+ const tint = iconColor ? document.createElementNS(svgNs, "svg") : null;
6427
+ const painted = tint ? document.createElementNS(svgNs, "image") : null;
6428
+ const inked = tint ? document.createElementNS(svgNs, "rect") : null;
6429
+ if (tint) {
6430
+ const maskId = `entity-label-tint-${++tintCounter}`;
6431
+ const mask = document.createElementNS(svgNs, "mask");
6432
+ mask.setAttribute("id", maskId);
6433
+ mask.setAttribute("maskUnits", "userSpaceOnUse");
6434
+ painted.setAttribute("href", this.options.iconUrl);
6435
+ painted.setAttribute("preserveAspectRatio", "xMidYMid meet");
6436
+ mask.appendChild(painted);
6437
+ inked.setAttribute("fill", iconColor);
6438
+ inked.setAttribute("mask", `url(#${maskId})`);
6439
+ tint.append(mask, inked);
6440
+ tint.style.display = "block";
6441
+ icon.style.display = "inline-flex";
6442
+ icon.appendChild(tint);
6425
6443
  }
6426
6444
  const resize = () => {
6427
6445
  if (this.disposed || this.counter !== counter) {
6428
6446
  return;
6429
6447
  }
6430
6448
  const intrinsic = this.options.iconScale != null && size == null;
6431
- target.style.width = `${(intrinsic ? image.naturalWidth || 24 : size || 24) * scale}px`;
6432
- target.style.height = `${(intrinsic ? image.naturalHeight || 24 : size || 24) * scale}px`;
6449
+ const width = (intrinsic ? image.naturalWidth || 24 : size || 24) * scale;
6450
+ const height = (intrinsic ? image.naturalHeight || 24 : size || 24) * scale;
6451
+ if (tint) {
6452
+ tint.setAttribute("viewBox", `0 0 ${width} ${height}`);
6453
+ for (const node of [tint, painted, inked]) {
6454
+ node.setAttribute("width", String(width));
6455
+ node.setAttribute("height", String(height));
6456
+ }
6457
+ }
6458
+ else {
6459
+ image.style.width = `${width}px`;
6460
+ image.style.height = `${height}px`;
6461
+ }
6433
6462
  this.viewer.scene.requestRender();
6434
6463
  };
6435
6464
  image.onload = resize;
6436
6465
  resize();
6437
6466
  image.src = this.options.iconUrl;
6438
- if (!iconColor) {
6467
+ if (!tint) {
6439
6468
  icon.appendChild(image);
6440
6469
  }
6441
6470
  }
@@ -6460,6 +6489,27 @@
6460
6489
  icon.style.minWidth = `${size}px`;
6461
6490
  icon.style.minHeight = `${size}px`;
6462
6491
  icon.style.boxSizing = "content-box";
6492
+ let fitted = false;
6493
+ fitIconBackground = () => {
6494
+ if (fitted) {
6495
+ return;
6496
+ }
6497
+ const padding = size / 3;
6498
+ const width = icon.offsetWidth - padding * 2;
6499
+ const height = icon.offsetHeight - padding * 2;
6500
+ if (width <= 0 || height <= 0) {
6501
+ return;
6502
+ }
6503
+ fitted = true;
6504
+ const longest = Math.max(width, height);
6505
+ const difference = (longest - Math.min(width, height)) / longest;
6506
+ if (difference > ICON_BACKGROUND_UNIFORM_TOLERANCE) {
6507
+ return;
6508
+ }
6509
+ icon.style.minWidth = `${longest}px`;
6510
+ icon.style.minHeight = `${longest}px`;
6511
+ this.viewer.scene.requestRender();
6512
+ };
6463
6513
  }
6464
6514
  if (this.options.iconScale === 0) {
6465
6515
  icon.style.display = "none";
@@ -6498,8 +6548,10 @@
6498
6548
  const countElement = document.createElement("span");
6499
6549
  countElement.className = "entity-label-count";
6500
6550
  countElement.style.cssText = "flex:none;align-self:center;white-space:nowrap;background:#fff;color:#000;text-shadow:none;border-radius:8px;padding:0 3px;display:none;";
6551
+ // Text above the icon would have the badge land in it, so the free corner is the lower one.
6552
+ const badgeBelow = presentation === "icon-text-above";
6501
6553
  if (iconContainer) {
6502
- countElement.style.cssText += "position:absolute;left:100%;top:0;transform:translate(-50%,-50%);";
6554
+ countElement.style.cssText += `position:absolute;left:100%;top:${badgeBelow ? "100%" : "0"};transform:translate(-50%,-50%);`;
6503
6555
  iconContainer.appendChild(countElement);
6504
6556
  }
6505
6557
  if (!layers.has(this.viewer)) {
@@ -6508,6 +6560,7 @@
6508
6560
  const layer = layers.get(this.viewer);
6509
6561
  let ele = null;
6510
6562
  let lineEle = null;
6563
+ let runEle = null;
6511
6564
  if (this.label) {
6512
6565
  ele = this.label;
6513
6566
  }
@@ -6544,6 +6597,7 @@
6544
6597
  }
6545
6598
  if (this.line) {
6546
6599
  lineEle = this.line;
6600
+ runEle = this.leaderRun;
6547
6601
  }
6548
6602
  else {
6549
6603
  lineEle = document.createElement("div");
@@ -6587,6 +6641,14 @@
6587
6641
  layer.element.appendChild(lineEleNode);
6588
6642
  layer.element.appendChild(lineEle);
6589
6643
  }
6644
+ // Built here rather than beside the stem, since the style can change on a label that is reused.
6645
+ if (angled && !runEle) {
6646
+ runEle = lineEle.cloneNode(false);
6647
+ runEle.className = "entity-label-leader-run";
6648
+ runEle.style.display = "none";
6649
+ this.leaderRun = runEle;
6650
+ layer.element.appendChild(runEle);
6651
+ }
6590
6652
  let MAX_DISTANCE = Math.max(15000, sizeInM * 35);
6591
6653
  if (visual && visual instanceof Cesium.Entity) {
6592
6654
  // Consistent sized things.
@@ -6606,6 +6668,25 @@
6606
6668
  y: upwards ? endY - height : endY, endY
6607
6669
  };
6608
6670
  };
6671
+ /*
6672
+ * An angled leader parks the label beside the anchor instead of over it,
6673
+ * so the stem rises to the label's middle and then runs out to whichever side has the room.
6674
+ */
6675
+ const fitAngledLeader = (anchorX, anchorY, desired, width, height, radius = 0) => {
6676
+ const canvas = this.viewer.canvas;
6677
+ const above = Math.max(0, anchorY - height / 2);
6678
+ const below = Math.max(0, canvas.clientHeight - anchorY - height / 2);
6679
+ const upwards = above >= Math.max(radius ? radius + 4 : 0, Math.min(8, desired)) || above >= below;
6680
+ const length = Math.min(desired, upwards ? above : below);
6681
+ const endY = anchorY + (upwards ? -length : length);
6682
+ const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
6683
+ const rightwards = anchorX + run + width <= canvas.clientWidth || anchorX - run - width < 0;
6684
+ return {
6685
+ x: Math.max(0, Math.min(rightwards ? anchorX + run : anchorX - run - width, canvas.clientWidth - width)),
6686
+ y: Math.max(0, Math.min(endY - height / 2, canvas.clientHeight - height)), endY,
6687
+ run, side: (rightwards ? "right" : "left")
6688
+ };
6689
+ };
6609
6690
  let cursorMotion = this.cursorMotion;
6610
6691
  const project = () => {
6611
6692
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
@@ -6743,7 +6824,14 @@
6743
6824
  y -= height / 2;
6744
6825
  }
6745
6826
  // Membership uses the unclamped source footprint, never the summary's changing dimensions.
6746
- if (leader)
6827
+ let leaderRun;
6828
+ let leaderSide;
6829
+ if (leader && angled) {
6830
+ const fit = fitAngledLeader(anchorX, anchorY, leaderLength, width, height);
6831
+ ({ x, y } = fit);
6832
+ ({ run: leaderRun, side: leaderSide } = fit);
6833
+ }
6834
+ else if (leader)
6747
6835
  ({ x, y } = fitLeader(anchorX, anchorY, leaderLength, width, height));
6748
6836
  const bounds = clusterBounds || { x, y, width, height };
6749
6837
  if (!leader && (bounds.x + bounds.width < 0 || bounds.y + bounds.height < 0 || bounds.x > this.viewer.canvas.clientWidth || bounds.y > this.viewer.canvas.clientHeight)) {
@@ -6758,20 +6846,21 @@
6758
6846
  extendedGeometry: visual instanceof Cesium.Entity && Boolean(visual.polygon || visual.polyline || visual.corridor),
6759
6847
  viewportWidth: this.viewer.canvas.clientWidth,
6760
6848
  id: this.id, entityKey: ((_s = this.rego) === null || _s === void 0 ? void 0 : _s.entityId) ? JSON.stringify([this.rego.entityId, this.rego.menuItemId || "", this.Transient && !this.Persistent ? "hover" : ""]) : undefined,
6761
- entityId: (_t = this.rego) === null || _t === void 0 ? void 0 : _t.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, distance,
6849
+ entityId: (_t = this.rego) === null || _t === void 0 ? void 0 : _t.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, leaderRun, leaderSide, distance,
6762
6850
  // Priority shows the hovered member individually without dragging its cluster along.
6763
6851
  opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
6764
6852
  };
6765
6853
  };
6766
6854
  let displacement = { x: 0, y: 0 };
6767
- let displayedSummaryMore = false;
6768
6855
  let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
6769
6856
  const apply = (placement) => {
6770
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
6857
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
6771
6858
  const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
6772
6859
  clickEntityIds = show ? placement.entityIds : [];
6773
6860
  ele.style.visibility = show ? "visible" : "hidden";
6774
6861
  lineEle.style.display = show && leader ? "block" : "none";
6862
+ if (runEle)
6863
+ runEle.style.display = show && leader ? "block" : "none";
6775
6864
  this.anchor.style.display = show && leader ? "block" : "none";
6776
6865
  if (!show) {
6777
6866
  displacement = { x: 0, y: 0 };
@@ -6782,17 +6871,8 @@
6782
6871
  return;
6783
6872
  }
6784
6873
  const displayText = (_a = placement.text) !== null && _a !== void 0 ? _a : originalText;
6785
- const summaryMore = Boolean(placement.summaryMore);
6786
- if (textElement && (textElement.textContent !== displayText + (summaryMore ? "\n…" : "") || displayedSummaryMore !== summaryMore)) {
6874
+ if (textElement && textElement.textContent !== displayText) {
6787
6875
  textElement.textContent = displayText;
6788
- if (summaryMore) {
6789
- const more = document.createElement("span");
6790
- more.className = "entity-label-more";
6791
- more.style.opacity = "0.65";
6792
- more.textContent = "\n…";
6793
- textElement.appendChild(more);
6794
- }
6795
- displayedSummaryMore = summaryMore;
6796
6876
  // Measure the summary's footprint next frame without replacing each member's source text.
6797
6877
  this.viewer.scene.requestRender();
6798
6878
  }
@@ -6810,6 +6890,7 @@
6810
6890
  // The badge participates in layout, so its new width needs another measurement.
6811
6891
  this.viewer.scene.requestRender();
6812
6892
  }
6893
+ fitIconBackground === null || fitIconBackground === void 0 ? void 0 : fitIconBackground();
6813
6894
  if (iconContainer) {
6814
6895
  const badgeWidth = countElement.offsetWidth;
6815
6896
  const iconWidth = iconElement.offsetWidth;
@@ -6817,7 +6898,9 @@
6817
6898
  const clearance = badgeWidth ? (narrow ? badgeWidth / 2 + 2 : Math.max(0, badgeWidth / 2 - iconWidth / 4)) : 0;
6818
6899
  countElement.style.left = `calc(100% + ${clearance}px)`;
6819
6900
  // Reserve the badge overhang so adjacent text and viewport edges remain clear.
6820
- iconContainer.style.marginTop = `${countElement.offsetHeight / 2}px`;
6901
+ const overhang = countElement.offsetHeight / 2;
6902
+ iconContainer.style.marginTop = badgeBelow ? "0px" : `${overhang}px`;
6903
+ iconContainer.style.marginBottom = badgeBelow ? `${overhang}px` : "0px";
6821
6904
  iconContainer.style.marginRight = `${badgeWidth / 2 + clearance}px`;
6822
6905
  }
6823
6906
  // Summary and badge changes must fit on their first visible frame, including after resize.
@@ -6860,19 +6943,26 @@
6860
6943
  sharedRing.animation = ringAnimation;
6861
6944
  }
6862
6945
  const radius = ringAnimation.inner > 0 ? ringAnimation.outer : 0;
6863
- const stem = (leader ? fitLeader(frame.anchorX, frame.anchorY, Math.max(frame.leaderLength, radius ? radius + 4 : 0), ele.offsetWidth / coordinates.scaleX, ele.offsetHeight / coordinates.scaleY, radius) : undefined);
6946
+ const stem = (leader ? (angled ? fitAngledLeader : fitLeader)(frame.anchorX, frame.anchorY, Math.max(frame.leaderLength, radius ? radius + 4 : 0), ele.offsetWidth / coordinates.scaleX, ele.offsetHeight / coordinates.scaleY, radius) : undefined);
6864
6947
  const position = { x: (stem || frame).x, y: (stem || frame).y };
6865
6948
  const width = ele.offsetWidth / coordinates.scaleX;
6866
6949
  const height = ele.offsetHeight / coordinates.scaleY;
6867
- if (leader && placement.textSide === "left") {
6950
+ if (leader && angled) {
6951
+ // The declutter owns the side; the fit only says which one had the room to begin with.
6952
+ const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
6953
+ const side = (_l = (_k = placement.textSide) !== null && _k !== void 0 ? _k : frame.leaderSide) !== null && _l !== void 0 ? _l : "right";
6954
+ const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
6955
+ position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
6956
+ }
6957
+ else if (leader && placement.textSide === "left") {
6868
6958
  position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
6869
6959
  }
6870
- const gap = (_l = (_k = this.options.pointerAvoidance) === null || _k === void 0 ? void 0 : _k.gap) !== null && _l !== void 0 ? _l : 12;
6960
+ const gap = (_o = (_m = this.options.pointerAvoidance) === null || _m === void 0 ? void 0 : _m.gap) !== null && _o !== void 0 ? _o : 12;
6871
6961
  const containsPointer = (x, y, margin) => {
6872
6962
  return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
6873
6963
  };
6874
6964
  const passive = !selectOnClick || !this.visualRegister;
6875
- if (passive && ((_m = this.options.pointerAvoidance) === null || _m === void 0 ? void 0 : _m.enabled) !== false && pointer &&
6965
+ if (passive && ((_p = this.options.pointerAvoidance) === null || _p === void 0 ? void 0 : _p.enabled) !== false && pointer &&
6876
6966
  (containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
6877
6967
  if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
6878
6968
  // Choose against the original box and retain the offset until the pointer leaves both boxes.
@@ -6890,7 +6980,7 @@
6890
6980
  else {
6891
6981
  displacement = { x: 0, y: 0 };
6892
6982
  }
6893
- const nudgeDuration = (_p = (_o = this.options.pointerAvoidance) === null || _o === void 0 ? void 0 : _o.transitionDuration) !== null && _p !== void 0 ? _p : 200;
6983
+ const nudgeDuration = (_r = (_q = this.options.pointerAvoidance) === null || _q === void 0 ? void 0 : _q.transitionDuration) !== null && _r !== void 0 ? _r : 200;
6894
6984
  const animateNudge = () => {
6895
6985
  const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
6896
6986
  const eased = progress * progress * (3 - 2 * progress);
@@ -6906,7 +6996,7 @@
6906
6996
  fromY: nudge.y,
6907
6997
  targetX: displacement.x,
6908
6998
  targetY: displacement.y,
6909
- start: now + ((_r = (_q = this.options.pointerAvoidance) === null || _q === void 0 ? void 0 : _q.delay) !== null && _r !== void 0 ? _r : 180)
6999
+ start: now + ((_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.delay) !== null && _t !== void 0 ? _t : 180)
6910
7000
  };
6911
7001
  }
6912
7002
  if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
@@ -6918,7 +7008,7 @@
6918
7008
  ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
6919
7009
  const clickTarget = content.querySelector('[role="button"]');
6920
7010
  if (clickTarget) {
6921
- clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_t = (_s = this.options.text) !== null && _s !== void 0 ? _s : text) !== null && _t !== void 0 ? _t : (_u = this.rego) === null || _u === void 0 ? void 0 : _u.entityId}`);
7011
+ clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_v = (_u = this.options.text) !== null && _u !== void 0 ? _u : text) !== null && _v !== void 0 ? _v : (_w = this.rego) === null || _w === void 0 ? void 0 : _w.entityId}`);
6922
7012
  }
6923
7013
  if (leader) {
6924
7014
  const nodeRadius = ringAnimation.outer;
@@ -6941,8 +7031,13 @@
6941
7031
  anchor.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
6942
7032
  anchor.style.zIndex = String(placement.zIndex);
6943
7033
  // The stem starts at the rim, never across the transparent centre, even after passive displacement.
6944
- const endX = Math.max(position.x, Math.min(frame.anchorX, position.x + width));
6945
- const endY = frame.anchorY < position.y ? position.y : frame.anchorY > position.y + height ? position.y + height : frame.anchorY;
7034
+ const straightX = Math.max(position.x, Math.min(frame.anchorX, position.x + width));
7035
+ const straightY = frame.anchorY < position.y ? position.y : frame.anchorY > position.y + height ? position.y + height : frame.anchorY;
7036
+ const beside = angled && (frame.anchorX < position.x || frame.anchorX > position.x + width);
7037
+ const elbowY = position.y + height / 2;
7038
+ const endX = beside ? frame.anchorX : straightX;
7039
+ const endY = beside ? elbowY : straightY;
7040
+ const runToX = frame.anchorX < position.x ? position.x : position.x + width;
6946
7041
  const dx = endX - frame.anchorX, dy = endY - frame.anchorY;
6947
7042
  const length = Math.hypot(dx, dy);
6948
7043
  const leaderAngle = Math.atan2(dy, dx);
@@ -6953,13 +7048,13 @@
6953
7048
  // Keep the stem inside the 126-degree arc, away from antialiased endpoints.
6954
7049
  const arcFraction = 0.35;
6955
7050
  const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
6956
- const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_v = ringAnimation.arcOffset) !== null && _v !== void 0 ? _v : 0);
7051
+ const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_x = ringAnimation.arcOffset) !== null && _x !== void 0 ? _x : 0);
6957
7052
  const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
6958
- const elapsed = Math.min(32, Math.max(0, now - ((_w = ringAnimation.arcTime) !== null && _w !== void 0 ? _w : now)));
7053
+ const elapsed = Math.min(32, Math.max(0, now - ((_y = ringAnimation.arcTime) !== null && _y !== void 0 ? _y : now)));
6959
7054
  const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
6960
7055
  const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
6961
- ringAnimation.arcOffset = approach((_x = ringAnimation.arcOffset) !== null && _x !== void 0 ? _x : 0, targetOffset);
6962
- ringAnimation.arcOpen = approach((_y = ringAnimation.arcOpen) !== null && _y !== void 0 ? _y : 0, targetOpen);
7056
+ ringAnimation.arcOffset = approach((_z = ringAnimation.arcOffset) !== null && _z !== void 0 ? _z : 0, targetOffset);
7057
+ ringAnimation.arcOpen = approach((_0 = ringAnimation.arcOpen) !== null && _0 !== void 0 ? _0 : 0, targetOpen);
6963
7058
  ringAnimation.arcTime = now;
6964
7059
  if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
6965
7060
  this.viewer.scene.requestRender();
@@ -6971,17 +7066,27 @@
6971
7066
  const cut = Math.min(radius, length);
6972
7067
  const startX = frame.anchorX + (length ? dx / length * cut : 0);
6973
7068
  const startY = frame.anchorY + (length ? dy / length * cut : 0);
6974
- const screenDx = (endX - startX) * coordinates.scaleX;
6975
- const screenDy = (endY - startY) * coordinates.scaleY;
6976
- lineEle.style.width = `${2 * coordinates.scaleX}px`;
6977
- lineEle.style.left = `${coordinates.x + (startX - 1) * coordinates.scaleX}px`;
6978
- lineEle.style.top = `${coordinates.y + startY * coordinates.scaleY}px`;
6979
- lineEle.style.height = `${Math.hypot(screenDx, screenDy)}px`;
6980
- lineEle.style.transform = `rotate(${Math.atan2(screenDy, screenDx) - Math.PI / 2}rad)`;
6981
- lineEle.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
6982
- lineEle.style.transformOrigin = "top center";
6983
- lineEle.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
6984
- lineEle.style.zIndex = String(placement.zIndex);
7069
+ const draw = (element, fromX, fromY, toX, toY) => {
7070
+ const screenDx = (toX - fromX) * coordinates.scaleX;
7071
+ const screenDy = (toY - fromY) * coordinates.scaleY;
7072
+ element.style.width = `${2 * coordinates.scaleX}px`;
7073
+ element.style.left = `${coordinates.x + (fromX - 1) * coordinates.scaleX}px`;
7074
+ element.style.top = `${coordinates.y + fromY * coordinates.scaleY}px`;
7075
+ element.style.height = `${Math.hypot(screenDx, screenDy)}px`;
7076
+ element.style.transform = `rotate(${Math.atan2(screenDy, screenDx) - Math.PI / 2}rad)`;
7077
+ element.style.boxShadow = radius ? "none" : "0 0 5px rgba(0, 0, 0, 0.3)";
7078
+ element.style.transformOrigin = "top center";
7079
+ element.style.opacity = String(opacity * (normalLeader ? 1 : cursorMotion.amount));
7080
+ element.style.zIndex = String(placement.zIndex);
7081
+ };
7082
+ draw(lineEle, startX, startY, endX, endY);
7083
+ if (runEle) {
7084
+ // Overlap the corner by the stem's own width so the elbow has no notch in it.
7085
+ const overlap = frame.anchorX < runToX ? -1 : 1;
7086
+ runEle.style.display = beside ? "block" : "none";
7087
+ if (beside)
7088
+ draw(runEle, endX + overlap, endY, runToX, endY);
7089
+ }
6985
7090
  }
6986
7091
  };
6987
7092
  ele.style.display = "flex";
@@ -7006,7 +7111,7 @@
7006
7111
  * Removes label from the viewer and unsubscribes from events.
7007
7112
  */
7008
7113
  removeLabel() {
7009
- var _a;
7114
+ var _a, _b;
7010
7115
  (_a = this.anchor) === null || _a === void 0 ? void 0 : _a.remove();
7011
7116
  this.anchor = null;
7012
7117
  if (this.label &&
@@ -7018,6 +7123,8 @@
7018
7123
  this.line.parentElement.removeChild(this.line);
7019
7124
  this.line = null;
7020
7125
  }
7126
+ (_b = this.leaderRun) === null || _b === void 0 ? void 0 : _b.remove();
7127
+ this.leaderRun = null;
7021
7128
  if (this.renderRemoval) {
7022
7129
  this.renderRemoval();
7023
7130
  this.renderRemoval = null;
@@ -16647,9 +16754,6 @@
16647
16754
  // Last date-time range taken from the clock.
16648
16755
  this.lastDateTimeMin = null;
16649
16756
  this.lastDateTimeMax = null;
16650
- // Entity ID -> last resolved date time.
16651
- // Helps determine if a slow resolved request can still be applied while we wait for the next one.
16652
- this.lastFoundDateTimes = new Map();
16653
16757
  // ID of Entity IDs that need to be requested.
16654
16758
  this.eIdQueue = [];
16655
16759
  // Membership mirror of eIdQueue for quick checks.
@@ -16681,8 +16785,12 @@
16681
16785
  // Unsubscribes the view monitor listener that queues what the camera has moved onto.
16682
16786
  this.monitorRemoval = null;
16683
16787
  // Indicates a tick is active.
16684
- // When true, we won't bother calling onTick again.
16788
+ // When true, a new tick is not started; it is noted in pendingTick and run once this one ends.
16685
16789
  this.runningTick = false;
16790
+ // A tick was asked for while one was running, so another is owed once it finishes.
16791
+ this.pendingTick = false;
16792
+ // The debounce every clock change goes through, kept so a tick owed after a running one does too.
16793
+ this.tickDelay = null;
16686
16794
  // Entity ID -> Entity blob.
16687
16795
  // This is a tool that lets developers set their own JSON blobs instead of requesting data from API.
16688
16796
  this.entityDataOverrides = new Map();
@@ -16746,11 +16854,13 @@
16746
16854
  let tickDelay = new BModels.DelayQueue(() => {
16747
16855
  this.onTick(false);
16748
16856
  }, 250, true);
16857
+ this.tickDelay = tickDelay;
16749
16858
  this.hDisposals.push(() => {
16750
16859
  if (tickDelay) {
16751
16860
  tickDelay.Dispose();
16752
16861
  }
16753
16862
  tickDelay = null;
16863
+ this.tickDelay = null;
16754
16864
  });
16755
16865
  // React to clock changes and request new Entities.
16756
16866
  let prevTime = null;
@@ -16790,7 +16900,6 @@
16790
16900
  this.priorityCount = 0;
16791
16901
  this.lastOrderFocus = null;
16792
16902
  this.allEIds.clear();
16793
- this.lastFoundDateTimes.clear();
16794
16903
  this.everFound.clear();
16795
16904
  this.clearIndex();
16796
16905
  this.lastDateTime = null;
@@ -17152,7 +17261,6 @@
17152
17261
  this.eIdQueued.clear();
17153
17262
  this.priorityCount = 0;
17154
17263
  this.allEIds.clear();
17155
- this.lastFoundDateTimes.clear();
17156
17264
  this.everFound.clear();
17157
17265
  this.clearIndex();
17158
17266
  this.entityDataOverrides.clear();
@@ -17168,7 +17276,13 @@
17168
17276
  }
17169
17277
  }
17170
17278
  onTick(force) {
17171
- if (this.runningTick || this.disposed) {
17279
+ if (this.disposed) {
17280
+ return;
17281
+ }
17282
+ if (this.runningTick) {
17283
+ if (this.historic) {
17284
+ this.pendingTick = true;
17285
+ }
17172
17286
  return;
17173
17287
  }
17174
17288
  const rTime = this.viewer.clock.currentTime.toString();
@@ -17176,10 +17290,16 @@
17176
17290
  return;
17177
17291
  }
17178
17292
  this.lastDateTime = rTime;
17293
+ // Whether the clock has left the moment this tick is reading.
17294
+ const movedOn = () => this.historic && this.viewer.clock.currentTime.toString() !== rTime;
17179
17295
  (async () => {
17180
17296
  this.runningTick = true;
17297
+ this.pendingTick = false;
17181
17298
  this.placeStragglers();
17182
17299
  let rerun = false;
17300
+ let interrupted = false;
17301
+ // Batch's answer is applied while the next batch is being read.
17302
+ const applying = [];
17183
17303
  try {
17184
17304
  const handleResponse = async (requestedBatch, entities) => {
17185
17305
  var _a, _b, _c, _d;
@@ -17243,45 +17363,7 @@
17243
17363
  api: this.api
17244
17364
  })).tags;
17245
17365
  }
17246
- // 0 = rTime is still the current time.
17247
- // 1 = rTime is in the future.
17248
- // -1 = rTime is in the past.
17249
- let changeDir = 0;
17250
- if (this.lastDateTime !== this.viewer.clock.currentTime.toString()) {
17251
- changeDir = this.lastDateTime < this.viewer.clock.currentTime.toString() ? 1 : -1;
17252
- }
17253
- let toEmit;
17254
- if (this.historic) {
17255
- // We can emit Entities if the last resolved time (per Entity) isn't newer than rTime.
17256
- // We compare using the changeDir to determine if we should emit or not.
17257
- toEmit = resolvedEntities.filter((e) => {
17258
- var _a;
17259
- const entityId = ((_a = e === null || e === void 0 ? void 0 : e.Bruce) === null || _a === void 0 ? void 0 : _a.ID) || (e === null || e === void 0 ? void 0 : e.id);
17260
- const lastFoundDateTime = this.lastFoundDateTimes.get(entityId);
17261
- if (lastFoundDateTime) {
17262
- if (changeDir === 1) {
17263
- return lastFoundDateTime <= rTime;
17264
- }
17265
- else if (changeDir === -1) {
17266
- return lastFoundDateTime >= rTime;
17267
- }
17268
- }
17269
- return true;
17270
- });
17271
- // Update the last found date times for the filtered Entities.
17272
- toEmit.forEach((e) => {
17273
- var _a;
17274
- const entityId = ((_a = e === null || e === void 0 ? void 0 : e.Bruce) === null || _a === void 0 ? void 0 : _a.ID) || (e === null || e === void 0 ? void 0 : e.id);
17275
- if (entityId) {
17276
- this.lastFoundDateTimes.set(entityId, rTime);
17277
- }
17278
- });
17279
- }
17280
- else {
17281
- // If we're not historic, we can just emit everything.
17282
- toEmit = resolvedEntities;
17283
- }
17284
- this.emitEntities(toEmit, tags);
17366
+ this.emitEntities(resolvedEntities, tags);
17285
17367
  };
17286
17368
  const QUEUE_BATCH_SIZE = 500;
17287
17369
  const requestedIds = new Set();
@@ -17290,7 +17372,8 @@
17290
17372
  const total = this.eIdQueue.length;
17291
17373
  let done = 0;
17292
17374
  while (this.eIdQueue.length && !this.disposed) {
17293
- if (this.historic && this.lastDateTime !== rTime) {
17375
+ if (movedOn()) {
17376
+ interrupted = true;
17294
17377
  break;
17295
17378
  }
17296
17379
  this.orderQueueByCamera();
@@ -17306,7 +17389,7 @@
17306
17389
  api: this.api,
17307
17390
  maxSearchTimeSec: 60 * 2
17308
17391
  });
17309
- handleResponse(batch, entities);
17392
+ applying.push(handleResponse(batch, entities));
17310
17393
  for (let i = 0; i < batch.length; i++) {
17311
17394
  requestedIds.add(batch[i]);
17312
17395
  }
@@ -17338,7 +17421,8 @@
17338
17421
  // The near ones are resolved first out of whatever the view still reaches.
17339
17422
  allEIds = this.bandIds(allEIds);
17340
17423
  while (allEIds.length && !this.disposed && !this.eIdQueue.length) {
17341
- if (this.historic && this.lastDateTime !== rTime) {
17424
+ if (movedOn()) {
17425
+ interrupted = true;
17342
17426
  break;
17343
17427
  }
17344
17428
  const batch = allEIds.splice(0, QUEUE_BATCH_SIZE);
@@ -17349,7 +17433,7 @@
17349
17433
  api: this.api,
17350
17434
  maxSearchTimeSec: 60 * 2
17351
17435
  });
17352
- handleResponse(batch, entities);
17436
+ applying.push(handleResponse(batch, entities));
17353
17437
  for (let i = 0; i < batch.length; i++) {
17354
17438
  requestedIds.add(batch[i]);
17355
17439
  }
@@ -17364,10 +17448,32 @@
17364
17448
  console.error(e);
17365
17449
  }
17366
17450
  finally {
17451
+ // Only a historic answer can be overtaken by a newer moment, so nothing else waits.
17452
+ if (this.historic && applying.length) {
17453
+ await Promise.all(applying.map((pending) => pending.catch((e) => console.error(e))));
17454
+ }
17367
17455
  this.runningTick = false;
17368
17456
  }
17369
- if (rerun) {
17457
+ // A moment read only in part is forgotten, so coming back to it reads it again in full.
17458
+ if (interrupted && this.lastDateTime === rTime) {
17459
+ this.lastDateTime = null;
17460
+ }
17461
+ if (this.disposed) {
17462
+ return;
17463
+ }
17464
+ // Leftovers at the same moment carry straight on, as they always have.
17465
+ if (rerun && !interrupted) {
17370
17466
  this.onTick(true);
17467
+ return;
17468
+ }
17469
+ if (interrupted || this.pendingTick || movedOn()) {
17470
+ this.pendingTick = false;
17471
+ if (this.tickDelay) {
17472
+ this.tickDelay.Call();
17473
+ }
17474
+ else {
17475
+ this.onTick(false);
17476
+ }
17371
17477
  }
17372
17478
  })();
17373
17479
  }
@@ -48962,7 +49068,7 @@ void main() {
48962
49068
  }
48963
49069
  EntityRenderEngine.GetGraphicShapeId = GetGraphicShapeId;
48964
49070
  async function Render(params) {
48965
- var _a, _b, _c;
49071
+ var _a, _b, _c, _d, _e;
48966
49072
  const groupRenderParams = {
48967
49073
  apiGetter: params.apiGetter,
48968
49074
  viewer: params.viewer,
@@ -49139,10 +49245,20 @@ void main() {
49139
49245
  polygons.push(entity);
49140
49246
  continue;
49141
49247
  }
49248
+ // Present when the gate above chose to reuse this entity's graphic.
49249
+ const existingRoot = cEntities.get(entity.Bruce.ID);
49250
+ const existingParts = new Map();
49251
+ (_d = existingRoot === null || existingRoot === void 0 ? void 0 : existingRoot._siblingGraphics) === null || _d === void 0 ? void 0 : _d.forEach((part) => {
49252
+ if (part._multiPartIndex != null) {
49253
+ existingParts.set(part._multiPartIndex, part);
49254
+ }
49255
+ });
49256
+ const partRendered = new Map();
49257
+ const partIndexes = new Map();
49142
49258
  const pParams = {
49143
49259
  ...groupRenderParams,
49144
49260
  entities: [],
49145
- rendered: cEntities
49261
+ rendered: partRendered
49146
49262
  };
49147
49263
  pParams.entitiesHistoric = params.entitiesHistoric;
49148
49264
  const zoomItem = pParams.zoomItems[entity.Bruce.ID];
@@ -49158,39 +49274,59 @@ void main() {
49158
49274
  };
49159
49275
  pParams.zoomItems[subEntity.Bruce.ID] = zoomItem;
49160
49276
  pParams.entities.push(subEntity);
49161
- }
49162
- const cPoly = await Polygon.RenderGroup(pParams);
49163
- Array.from(cPoly.keys()).forEach((key) => {
49164
- if (cPoly.get(key)) {
49165
- pParams.entities = pParams.entities.filter((e) => e.Bruce.ID != key);
49166
- }
49167
- });
49168
- let rendered = Array.from(cPoly.values());
49169
- const cLines = await Polyline.RenderGroup(pParams);
49170
- Array.from(cLines.keys()).forEach((key) => {
49171
- if (cLines.get(key)) {
49277
+ partIndexes.set(subEntity.Bruce.ID, j);
49278
+ const existingPart = existingParts.get(j);
49279
+ if (existingPart) {
49280
+ // An engine preparing a reused graphic gathers everything connected to it, so a part still
49281
+ // attached to its root would have every other part evicted from the scene.
49282
+ existingPart._parentEntity = null;
49283
+ partRendered.set(subEntity.Bruce.ID, existingPart);
49284
+ }
49285
+ }
49286
+ const rendered = [];
49287
+ const collect = (results) => {
49288
+ results.forEach((cEntity, key) => {
49289
+ if (!cEntity) {
49290
+ return;
49291
+ }
49292
+ cEntity._multiPartIndex = partIndexes.get(key);
49293
+ rendered.push(cEntity);
49172
49294
  pParams.entities = pParams.entities.filter((e) => e.Bruce.ID != key);
49173
- }
49174
- });
49175
- rendered = rendered.concat(Array.from(cLines.values()));
49295
+ });
49296
+ };
49297
+ collect(await Polygon.RenderGroup(pParams));
49298
+ collect(await Polyline.RenderGroup(pParams));
49176
49299
  // We'll only render points when there's no other geometry.
49177
49300
  // Generally speaking when polygons/polylines are drawn, points are only drawn because of invalid data.
49178
49301
  if (!rendered.length) {
49179
- const cPoints = await Point.RenderGroup(pParams);
49180
- rendered = rendered.concat(Array.from(cPoints.values()));
49302
+ collect(await Point.RenderGroup(pParams));
49181
49303
  }
49182
- rendered = rendered.filter(x => x != null);
49183
49304
  if (rendered.length) {
49184
- const rootEntity = new Cesium.Entity({
49185
- id: BModels.ObjectUtils.UId(15)
49186
- });
49187
- rootEntity._siblingGraphics = [];
49305
+ const sameParts = ((_e = existingRoot === null || existingRoot === void 0 ? void 0 : existingRoot._siblingGraphics) === null || _e === void 0 ? void 0 : _e.length) == rendered.length &&
49306
+ rendered.every((part) => existingRoot._siblingGraphics.includes(part));
49307
+ let rootEntity;
49308
+ if (sameParts) {
49309
+ rootEntity = existingRoot;
49310
+ }
49311
+ else {
49312
+ if (existingRoot === null || existingRoot === void 0 ? void 0 : existingRoot._siblingGraphics) {
49313
+ // Retiring the old root removes its parts, and the ones carried over belong to the new root now.
49314
+ existingRoot._siblingGraphics = existingRoot._siblingGraphics.filter((part) => !rendered.includes(part));
49315
+ }
49316
+ rootEntity = new Cesium.Entity({
49317
+ id: BModels.ObjectUtils.UId(15)
49318
+ });
49319
+ }
49320
+ rootEntity._siblingGraphics = rendered;
49188
49321
  rootEntity._renderGroup = GetRenderGroupId(zoomItem);
49189
- rootEntity._siblingGraphics = rootEntity._siblingGraphics.concat(rendered);
49322
+ // The skip gate compares the rego's style against the zoom item's, and the rego takes it from here.
49323
+ rootEntity.styleId = (zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) == -1 ? -1 : (+(zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) || null);
49324
+ rootEntity.styleEffective = rendered[0].styleEffective;
49190
49325
  cEntities.set(entity.Bruce.ID, rootEntity);
49191
- const firstEntity = rendered[0];
49192
- if (firstEntity) {
49193
- rootEntity.position = GetCValue(params.viewer, firstEntity.position);
49326
+ const newPosition = GetCValue(params.viewer, rendered[0].position);
49327
+ const oldPosition = GetCValue(params.viewer, rootEntity.position);
49328
+ if (newPosition && (!oldPosition || !BModels.Cartes.IsEqualCartes3(oldPosition, newPosition))) {
49329
+ rootEntity.position = new Cesium.ConstantPositionProperty(newPosition);
49194
49330
  }
49195
49331
  rendered.forEach((child) => {
49196
49332
  child._parentEntity = rootEntity;
@@ -50124,7 +50260,7 @@ void main() {
50124
50260
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
50125
50261
  })(exports.StyleUtils || (exports.StyleUtils = {}));
50126
50262
 
50127
- const VERSION = "7.4.1";
50263
+ const VERSION = "7.4.3";
50128
50264
  /**
50129
50265
  * Updates the environment instance used by bruce-cesium to one specified.
50130
50266
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.