bruce-cesium 7.4.2 → 7.4.4
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.
- package/dist/bruce-cesium.es5.js +435 -234
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +433 -232
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/rendering/entity-gatherer.js +50 -51
- package/dist/lib/rendering/entity-gatherer.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine.js +56 -26
- package/dist/lib/rendering/entity-render-engine.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js +325 -153
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/rendering/entity-gatherer.d.ts +2 -1
- package/dist/types/rendering/entity-render-engine.d.ts +1 -0
- package/dist/types/rendering/render-managers/common/entity-label.d.ts +6 -0
- package/package.json +1 -1
package/dist/bruce-cesium.umd.js
CHANGED
|
@@ -5387,8 +5387,16 @@
|
|
|
5387
5387
|
// so a row of labels reads as one size rather than a set of differently shaped pills.
|
|
5388
5388
|
const ICON_BACKGROUND_UNIFORM_TOLERANCE = 0.3;
|
|
5389
5389
|
const TEXT_MAX_WIDTH = 200;
|
|
5390
|
+
// Distinct icons a cluster shows, the representative's included.
|
|
5391
|
+
const ICON_STACK_LIMIT = 3;
|
|
5392
|
+
// Size of every icon in a cluster's arrangement, relative to the representative's own.
|
|
5393
|
+
const ICON_STACK_SCALE = 0.6;
|
|
5390
5394
|
// How far an angled leader runs sideways before the label starts.
|
|
5391
5395
|
const ANGLED_LEADER_RUN = 28;
|
|
5396
|
+
// Clearance between a point or billboard glyph and the ring drawn around it.
|
|
5397
|
+
const ICON_RING_PADDING = 4;
|
|
5398
|
+
// Small points still get a ring as large as the default near ring.
|
|
5399
|
+
const ICON_RING_MIN = 10;
|
|
5392
5400
|
// One per tinted icon, since an SVG mask is addressed by id across the whole document.
|
|
5393
5401
|
let tintCounter = 0;
|
|
5394
5402
|
// Key where the label instance is stored within the cesium entity.
|
|
@@ -5570,6 +5578,28 @@
|
|
|
5570
5578
|
* Prefer declared semantics; only infers from recognisable glyphs and filename tokens, never image pixels.
|
|
5571
5579
|
*/
|
|
5572
5580
|
EntityLabel.IconPriority = GetClusterIconPriority;
|
|
5581
|
+
function iconLook(options) {
|
|
5582
|
+
return {
|
|
5583
|
+
icon: options.icon || undefined,
|
|
5584
|
+
iconUrl: options.iconUrl || undefined,
|
|
5585
|
+
iconClass: typeof options.iconClass === "string" && options.iconClass ? options.iconClass : undefined,
|
|
5586
|
+
iconColor: typeof options.iconColor === "string" && options.iconColor ? options.iconColor : undefined,
|
|
5587
|
+
iconBackground: options.iconBackground || undefined
|
|
5588
|
+
};
|
|
5589
|
+
}
|
|
5590
|
+
/*
|
|
5591
|
+
* Everything that makes two labels look alike, so labels from different groups only merge when they would read the same.
|
|
5592
|
+
*/
|
|
5593
|
+
function clusterLook(options) {
|
|
5594
|
+
var _a;
|
|
5595
|
+
const presentation = options.presentation || "text";
|
|
5596
|
+
return JSON.stringify([
|
|
5597
|
+
presentation,
|
|
5598
|
+
(_a = options.leader) !== null && _a !== void 0 ? _a : null,
|
|
5599
|
+
options.leaderStyle || "default", options.leaderColor || "",
|
|
5600
|
+
presentation === "text" ? null : iconLook(options)
|
|
5601
|
+
]);
|
|
5602
|
+
}
|
|
5573
5603
|
/*
|
|
5574
5604
|
* Resolves clustering and stable text placement before depth fading; highlighted labels receive priority.
|
|
5575
5605
|
*/
|
|
@@ -5591,7 +5621,8 @@
|
|
|
5591
5621
|
if (!((_b = (_a = frame.options) === null || _a === void 0 ? void 0 : _a.cluster) === null || _b === void 0 ? void 0 : _b.enabled) || frame.priority > 0) {
|
|
5592
5622
|
continue;
|
|
5593
5623
|
}
|
|
5594
|
-
|
|
5624
|
+
// Groups meet inside a presentation; which of them may merge is decided pairwise below.
|
|
5625
|
+
const key = frame.options.presentation || "text";
|
|
5595
5626
|
if (!groups.has(key)) {
|
|
5596
5627
|
groups.set(key, []);
|
|
5597
5628
|
}
|
|
@@ -5603,32 +5634,54 @@
|
|
|
5603
5634
|
frame.options.cluster.pixelRange == null ? { ...frame, x: frame.anchorX, y: frame.anchorY, width: 1, height: 1 } :
|
|
5604
5635
|
frame.clusterBounds ? { ...frame, ...frame.clusterBounds } : frame);
|
|
5605
5636
|
const index = new ScreenIndex();
|
|
5606
|
-
|
|
5637
|
+
const rank = new Map();
|
|
5638
|
+
const looks = new Map();
|
|
5639
|
+
candidates.forEach((frame, i) => {
|
|
5607
5640
|
index.Add(frame);
|
|
5608
|
-
|
|
5641
|
+
rank.set(frame.id, i);
|
|
5642
|
+
looks.set(frame.id, clusterLook(frame.options));
|
|
5643
|
+
});
|
|
5609
5644
|
const clusters = ClusterItems({
|
|
5610
5645
|
items: candidates,
|
|
5611
5646
|
id: frame => frame.id,
|
|
5612
5647
|
minSize: group[0].options.cluster.minSize,
|
|
5613
5648
|
neighbours: frame => {
|
|
5614
5649
|
var _a;
|
|
5615
|
-
|
|
5650
|
+
const nearby = index.Query(frame, (_a = frame.options.cluster.pixelRange) !== null && _a !== void 0 ? _a : (frame.options.presentation === "icon" ? 24 : 8)).sort((a, b) => rank.get(a.id) - rank.get(b.id));
|
|
5651
|
+
return compatibleMembers(frame, nearby, other => other.group || "", other => looks.get(other.id));
|
|
5616
5652
|
}
|
|
5617
5653
|
});
|
|
5618
5654
|
for (const cluster of clusters) {
|
|
5619
5655
|
let chosen = cluster.representative;
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
}
|
|
5628
|
-
}
|
|
5656
|
+
const iconic = (chosen.options.presentation || "text") !== "text";
|
|
5657
|
+
// Members in the order they would win representation; the seed is the nearest.
|
|
5658
|
+
let ordered = [chosen, ...cluster.members.filter(member => member !== chosen)];
|
|
5659
|
+
if (iconic && chosen.options.cluster.representative !== "nearest") {
|
|
5660
|
+
ordered = ordered.map((member, i) => ({ member, i, priority: EntityLabel.IconPriority(member.options) }))
|
|
5661
|
+
.sort((a, b) => b.priority - a.priority || a.i - b.i).map(entry => entry.member);
|
|
5662
|
+
chosen = ordered[0];
|
|
5629
5663
|
}
|
|
5630
5664
|
const representative = result.get(chosen.id);
|
|
5631
5665
|
representative.members = cluster.members.map(frame => frame.id);
|
|
5666
|
+
if (iconic && chosen.options.iconScale !== 0) {
|
|
5667
|
+
const seen = new Set();
|
|
5668
|
+
const icons = [];
|
|
5669
|
+
for (const member of ordered) {
|
|
5670
|
+
const look = iconLook(member.options);
|
|
5671
|
+
const key = JSON.stringify(look);
|
|
5672
|
+
if (seen.has(key)) {
|
|
5673
|
+
continue;
|
|
5674
|
+
}
|
|
5675
|
+
seen.add(key);
|
|
5676
|
+
if (member !== ordered[0])
|
|
5677
|
+
icons.push(look);
|
|
5678
|
+
if (seen.size >= ICON_STACK_LIMIT) {
|
|
5679
|
+
break;
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
if (icons.length)
|
|
5683
|
+
representative.icons = icons;
|
|
5684
|
+
}
|
|
5632
5685
|
representative.entityIds = Array.from(new Set(cluster.members.map(frame => frame.entityId).filter(Boolean)));
|
|
5633
5686
|
if (chosen.options.presentation !== "icon") {
|
|
5634
5687
|
const values = new Set();
|
|
@@ -5681,6 +5734,34 @@
|
|
|
5681
5734
|
return Array.from(result.values());
|
|
5682
5735
|
}
|
|
5683
5736
|
EntityLabel.Layout = Layout;
|
|
5737
|
+
/*
|
|
5738
|
+
* The seed plus every neighbour, in order, that shares a group or a look with each member accepted so far.
|
|
5739
|
+
*
|
|
5740
|
+
* Checking against the whole accepted set stops a group with mixed icons bridging two groups that look different.
|
|
5741
|
+
*/
|
|
5742
|
+
function compatibleMembers(seed, nearby, group, look) {
|
|
5743
|
+
const accepted = [seed];
|
|
5744
|
+
const byGroup = new Map([[group(seed), 1]]);
|
|
5745
|
+
const byLook = new Map([[look(seed), 1]]);
|
|
5746
|
+
const byBoth = new Map([[JSON.stringify([group(seed), look(seed)]), 1]]);
|
|
5747
|
+
for (const item of nearby) {
|
|
5748
|
+
if (item === seed) {
|
|
5749
|
+
continue;
|
|
5750
|
+
}
|
|
5751
|
+
const itemGroup = group(item), itemLook = look(item), both = JSON.stringify([itemGroup, itemLook]);
|
|
5752
|
+
// Accepted members in another group with another look are the ones this item may not sit beside.
|
|
5753
|
+
const conflicting = accepted.length - (byGroup.get(itemGroup) || 0) - (byLook.get(itemLook) || 0) + (byBoth.get(both) || 0);
|
|
5754
|
+
if (conflicting > 0) {
|
|
5755
|
+
continue;
|
|
5756
|
+
}
|
|
5757
|
+
accepted.push(item);
|
|
5758
|
+
byGroup.set(itemGroup, (byGroup.get(itemGroup) || 0) + 1);
|
|
5759
|
+
byLook.set(itemLook, (byLook.get(itemLook) || 0) + 1);
|
|
5760
|
+
byBoth.set(both, (byBoth.get(both) || 0) + 1);
|
|
5761
|
+
}
|
|
5762
|
+
return accepted;
|
|
5763
|
+
}
|
|
5764
|
+
EntityLabel.compatibleMembers = compatibleMembers;
|
|
5684
5765
|
const layers = new WeakMap();
|
|
5685
5766
|
const containers = new WeakMap();
|
|
5686
5767
|
/*
|
|
@@ -6078,7 +6159,7 @@
|
|
|
6078
6159
|
* @param starterPos3d optional position to create label at. Ignored for billboard labels.
|
|
6079
6160
|
*/
|
|
6080
6161
|
async createLabel(starterPos3d) {
|
|
6081
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o
|
|
6162
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
6082
6163
|
this.removeLabel();
|
|
6083
6164
|
let pos3d = starterPos3d ? starterPos3d.clone() : null;
|
|
6084
6165
|
const counter = ++this.counter;
|
|
@@ -6400,125 +6481,28 @@
|
|
|
6400
6481
|
let iconElement;
|
|
6401
6482
|
let iconContainer;
|
|
6402
6483
|
let fitIconBackground;
|
|
6484
|
+
let iconStack;
|
|
6485
|
+
let stackedFits = [];
|
|
6486
|
+
let displayedStack = "[]";
|
|
6403
6487
|
content.className = "entity-label-content";
|
|
6404
6488
|
content.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
6405
6489
|
if (presentation === "icon-text-above") {
|
|
6406
6490
|
content.style.flexDirection = "column-reverse";
|
|
6407
6491
|
}
|
|
6408
6492
|
if (presentation !== "text") {
|
|
6409
|
-
const
|
|
6410
|
-
iconElement = icon;
|
|
6411
|
-
|
|
6412
|
-
icon.style.cssText = `font-size:${(this.options.iconSize || 24) * ((_m = this.options.iconScale) !== null && _m !== void 0 ? _m : 1)}px;line-height:1;`;
|
|
6413
|
-
icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
|
|
6414
|
-
const iconColor = typeof this.options.iconColor === "string" ? this.options.iconColor : undefined;
|
|
6415
|
-
const iconBackground = this.options.iconBackground && this.options.iconScale !== 0;
|
|
6416
|
-
if (iconColor) {
|
|
6417
|
-
icon.style.color = iconColor;
|
|
6418
|
-
}
|
|
6419
|
-
if (this.options.iconUrl) {
|
|
6420
|
-
const image = document.createElement("img");
|
|
6421
|
-
image.draggable = false;
|
|
6422
|
-
image.alt = this.options.icon || "";
|
|
6423
|
-
const scale = (_o = this.options.iconScale) !== null && _o !== void 0 ? _o : 1;
|
|
6424
|
-
const size = this.options.iconSize;
|
|
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);
|
|
6443
|
-
}
|
|
6444
|
-
const resize = () => {
|
|
6445
|
-
if (this.disposed || this.counter !== counter) {
|
|
6446
|
-
return;
|
|
6447
|
-
}
|
|
6448
|
-
const intrinsic = this.options.iconScale != null && size == null;
|
|
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
|
-
}
|
|
6462
|
-
this.viewer.scene.requestRender();
|
|
6463
|
-
};
|
|
6464
|
-
image.onload = resize;
|
|
6465
|
-
resize();
|
|
6466
|
-
image.src = this.options.iconUrl;
|
|
6467
|
-
if (!tint) {
|
|
6468
|
-
icon.appendChild(image);
|
|
6469
|
-
}
|
|
6470
|
-
}
|
|
6471
|
-
else if (typeof this.options.iconClass === "string" && this.options.iconClass) {
|
|
6472
|
-
icon.className = `entity-label-icon ${this.options.iconClass}`;
|
|
6473
|
-
}
|
|
6474
|
-
else {
|
|
6475
|
-
icon.textContent = this.options.icon || "●";
|
|
6476
|
-
}
|
|
6477
|
-
if (iconBackground) {
|
|
6478
|
-
const color = Cesium.Color.fromCssColorString(iconColor || "white") || Cesium.Color.WHITE;
|
|
6479
|
-
const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
|
|
6480
|
-
const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Cesium.Color()) :
|
|
6481
|
-
color.brighten(ICON_BACKGROUND_BLEND, new Cesium.Color()));
|
|
6482
|
-
const size = (this.options.iconSize || 24) * ((_p = this.options.iconScale) !== null && _p !== void 0 ? _p : 1);
|
|
6483
|
-
icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
|
|
6484
|
-
icon.style.borderRadius = "50%";
|
|
6485
|
-
icon.style.padding = `${size / 3}px`;
|
|
6486
|
-
icon.style.display = "inline-flex";
|
|
6487
|
-
icon.style.alignItems = "center";
|
|
6488
|
-
icon.style.justifyContent = "center";
|
|
6489
|
-
icon.style.minWidth = `${size}px`;
|
|
6490
|
-
icon.style.minHeight = `${size}px`;
|
|
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
|
-
};
|
|
6513
|
-
}
|
|
6514
|
-
if (this.options.iconScale === 0) {
|
|
6515
|
-
icon.style.display = "none";
|
|
6516
|
-
}
|
|
6493
|
+
const built = this.buildIcon(this.options, counter);
|
|
6494
|
+
const icon = iconElement = built.icon;
|
|
6495
|
+
fitIconBackground = built.fit;
|
|
6517
6496
|
makeClickable(icon);
|
|
6518
6497
|
// Keep badges outside the glyph so font classes and image masks cannot affect them.
|
|
6519
6498
|
iconContainer = document.createElement("span");
|
|
6520
|
-
iconContainer.style.cssText = "position:relative;display:inline-flex;flex:none;";
|
|
6521
|
-
|
|
6499
|
+
iconContainer.style.cssText = "position:relative;display:inline-flex;flex:none;align-items:center;";
|
|
6500
|
+
icon.style.position = "relative";
|
|
6501
|
+
iconStack = document.createElement("span");
|
|
6502
|
+
iconStack.className = "entity-label-icon-stack";
|
|
6503
|
+
iconStack.style.cssText = "display:none;gap:2px;align-items:center;justify-items:center;flex:none;";
|
|
6504
|
+
makeClickable(iconStack);
|
|
6505
|
+
iconContainer.append(iconStack, icon);
|
|
6522
6506
|
content.appendChild(iconContainer);
|
|
6523
6507
|
}
|
|
6524
6508
|
if (presentation !== "icon") {
|
|
@@ -6530,7 +6514,7 @@
|
|
|
6530
6514
|
if (presentation === "text")
|
|
6531
6515
|
makeClickable(textElement);
|
|
6532
6516
|
content.appendChild(textElement);
|
|
6533
|
-
if ((
|
|
6517
|
+
if ((_m = this.options.cluster) === null || _m === void 0 ? void 0 : _m.enabled) {
|
|
6534
6518
|
sourceText = document.createElement("span");
|
|
6535
6519
|
sourceText.className = "entity-label-source-text";
|
|
6536
6520
|
sourceText.setAttribute("aria-hidden", "true");
|
|
@@ -6571,7 +6555,7 @@
|
|
|
6571
6555
|
ele.appendChild(countElement);
|
|
6572
6556
|
}
|
|
6573
6557
|
ele.className = "entity-label";
|
|
6574
|
-
ele.dataset.entityId = ((
|
|
6558
|
+
ele.dataset.entityId = ((_o = this.rego) === null || _o === void 0 ? void 0 : _o.entityId) || "";
|
|
6575
6559
|
ele.dataset.presentation = presentation;
|
|
6576
6560
|
if (this.groupId) {
|
|
6577
6561
|
ele.dataset.groupId = this.groupId;
|
|
@@ -6687,6 +6671,41 @@
|
|
|
6687
6671
|
run, side: (rightwards ? "right" : "left")
|
|
6688
6672
|
};
|
|
6689
6673
|
};
|
|
6674
|
+
/*
|
|
6675
|
+
* The on-screen centre and half-size of a point or billboard glyph, relative to its projected position.
|
|
6676
|
+
* Read every frame, since styling and selection can rescale a billboard in place.
|
|
6677
|
+
*/
|
|
6678
|
+
const measureIcon = () => {
|
|
6679
|
+
var _a;
|
|
6680
|
+
if (!normalLeader || !(visual instanceof Cesium.Entity)) {
|
|
6681
|
+
return null;
|
|
6682
|
+
}
|
|
6683
|
+
if (visual.point) {
|
|
6684
|
+
const size = EnsureNumber(getValue(this.viewer, visual.point.pixelSize), 1) +
|
|
6685
|
+
EnsureNumber(getValue(this.viewer, visual.point.outlineWidth), 0) * 2;
|
|
6686
|
+
return size > 0 ? { x: 0, y: 0, radius: size / 2 } : null;
|
|
6687
|
+
}
|
|
6688
|
+
if (!visual.billboard) {
|
|
6689
|
+
return null;
|
|
6690
|
+
}
|
|
6691
|
+
const billboard = visual.billboard;
|
|
6692
|
+
const scale = EnsureNumber(getValue(this.viewer, billboard.scale), 1);
|
|
6693
|
+
// Icon billboards only record their image height; generated circles set both sides.
|
|
6694
|
+
const height = EnsureNumber((_a = getValue(this.viewer, billboard.height)) !== null && _a !== void 0 ? _a : billboard._billboardSize, 0) * scale;
|
|
6695
|
+
const width = EnsureNumber(getValue(this.viewer, billboard.width), height / scale) * scale;
|
|
6696
|
+
if (!(height > 0) || !(width > 0)) {
|
|
6697
|
+
return null;
|
|
6698
|
+
}
|
|
6699
|
+
const vertical = getValue(this.viewer, billboard.verticalOrigin);
|
|
6700
|
+
const horizontal = getValue(this.viewer, billboard.horizontalOrigin);
|
|
6701
|
+
const offset = getValue(this.viewer, billboard.pixelOffset);
|
|
6702
|
+
return {
|
|
6703
|
+
x: ((offset === null || offset === void 0 ? void 0 : offset.x) || 0) + (horizontal === Cesium.HorizontalOrigin.LEFT ? width / 2 : horizontal === Cesium.HorizontalOrigin.RIGHT ? -width / 2 : 0),
|
|
6704
|
+
y: ((offset === null || offset === void 0 ? void 0 : offset.y) || 0) + (vertical === Cesium.VerticalOrigin.BOTTOM || vertical === Cesium.VerticalOrigin.BASELINE ? -height / 2 :
|
|
6705
|
+
vertical === Cesium.VerticalOrigin.TOP ? height / 2 : 0),
|
|
6706
|
+
radius: Math.max(width, height) / 2
|
|
6707
|
+
};
|
|
6708
|
+
};
|
|
6690
6709
|
let cursorMotion = this.cursorMotion;
|
|
6691
6710
|
const project = () => {
|
|
6692
6711
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
@@ -6753,9 +6772,13 @@
|
|
|
6753
6772
|
this.cursorMotion = cursorMotion;
|
|
6754
6773
|
const cursorAmount = cursorMotion.amount;
|
|
6755
6774
|
leader = normalLeader || cursorAmount > 0;
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6775
|
+
// An icon keeps its callout centred on the glyph rather than following the cursor across it.
|
|
6776
|
+
const icon = measureIcon();
|
|
6777
|
+
const iconRing = icon ? Math.max(ICON_RING_MIN, icon.radius + ICON_RING_PADDING) : 0;
|
|
6778
|
+
const pivot = icon ? undefined : transientPivot;
|
|
6779
|
+
let x = ((_l = pivot === null || pivot === void 0 ? void 0 : pivot.x) !== null && _l !== void 0 ? _l : anchor.x) + (icon || pixelOffset).x;
|
|
6780
|
+
let y = ((_m = pivot === null || pivot === void 0 ? void 0 : pivot.y) !== null && _m !== void 0 ? _m : anchor.y) + (icon || pixelOffset).y;
|
|
6781
|
+
if (cursorAmount > 0 && !icon) {
|
|
6759
6782
|
// A leader-less label starts centred on its anchor; preserve that box as it lifts away.
|
|
6760
6783
|
if (!normalLeader) {
|
|
6761
6784
|
x -= ele.offsetWidth / layer.coordinates.scaleX / 2;
|
|
@@ -6793,9 +6816,11 @@
|
|
|
6793
6816
|
if (cursorAmount > 0) {
|
|
6794
6817
|
leaderLength += (96 - leaderLength) * cursorAmount;
|
|
6795
6818
|
}
|
|
6819
|
+
// Measured from the ring, so the visible stem is the same length whatever the glyph's size.
|
|
6820
|
+
leaderLength += iconRing;
|
|
6796
6821
|
y = anchorY - leaderLength;
|
|
6797
6822
|
}
|
|
6798
|
-
else if (hover && !reused) {
|
|
6823
|
+
else if (hover && !reused && !icon) {
|
|
6799
6824
|
x = hover.x + pixelOffset.x;
|
|
6800
6825
|
y = hover.y + pixelOffset.y - 24;
|
|
6801
6826
|
}
|
|
@@ -6847,6 +6872,7 @@
|
|
|
6847
6872
|
viewportWidth: this.viewer.canvas.clientWidth,
|
|
6848
6873
|
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,
|
|
6849
6874
|
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,
|
|
6875
|
+
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius,
|
|
6850
6876
|
// Priority shows the hovered member individually without dragging its cluster along.
|
|
6851
6877
|
opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
6852
6878
|
};
|
|
@@ -6854,7 +6880,7 @@
|
|
|
6854
6880
|
let displacement = { x: 0, y: 0 };
|
|
6855
6881
|
let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
6856
6882
|
const apply = (placement) => {
|
|
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;
|
|
6883
|
+
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, _1, _2;
|
|
6858
6884
|
const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
|
|
6859
6885
|
clickEntityIds = show ? placement.entityIds : [];
|
|
6860
6886
|
ele.style.visibility = show ? "visible" : "hidden";
|
|
@@ -6891,6 +6917,29 @@
|
|
|
6891
6917
|
this.viewer.scene.requestRender();
|
|
6892
6918
|
}
|
|
6893
6919
|
fitIconBackground === null || fitIconBackground === void 0 ? void 0 : fitIconBackground();
|
|
6920
|
+
const stack = JSON.stringify(placement.icons || []);
|
|
6921
|
+
if (iconStack && stack !== displayedStack) {
|
|
6922
|
+
displayedStack = stack;
|
|
6923
|
+
const looks = ((_b = placement.icons) === null || _b === void 0 ? void 0 : _b.length) ? [iconLook(this.options), ...placement.icons] : [];
|
|
6924
|
+
// One small size for all, so no icon in the arrangement reads as more important than another.
|
|
6925
|
+
const size = (this.options.iconSize || 24) * ((_c = this.options.iconScale) !== null && _c !== void 0 ? _c : 1) * ICON_STACK_SCALE;
|
|
6926
|
+
const built = looks.map(look => this.buildIcon({ ...look, iconSize: size }, counter));
|
|
6927
|
+
// Two sit side by side; three make a triangle with the representative on top.
|
|
6928
|
+
iconStack.style.gridTemplateColumns = built.length === 3 ? "auto auto" : `repeat(${built.length}, auto)`;
|
|
6929
|
+
if (built.length === 3) {
|
|
6930
|
+
built[0].icon.style.gridColumn = "1 / span 2";
|
|
6931
|
+
}
|
|
6932
|
+
iconStack.replaceChildren(...built.map(({ icon }) => icon));
|
|
6933
|
+
iconStack.style.display = built.length ? "inline-grid" : "none";
|
|
6934
|
+
// The glyph stays measurable, since cluster membership is taken from its footprint.
|
|
6935
|
+
iconElement.style.visibility = built.length ? "hidden" : "";
|
|
6936
|
+
iconElement.style.position = built.length ? "absolute" : "relative";
|
|
6937
|
+
stackedFits = built.map(({ fit }) => fit).filter(Boolean);
|
|
6938
|
+
// The stack widens the label, which the next frame has to measure.
|
|
6939
|
+
this.viewer.scene.requestRender();
|
|
6940
|
+
}
|
|
6941
|
+
for (const fit of stackedFits)
|
|
6942
|
+
fit();
|
|
6894
6943
|
if (iconContainer) {
|
|
6895
6944
|
const badgeWidth = countElement.offsetWidth;
|
|
6896
6945
|
const iconWidth = iconElement.offsetWidth;
|
|
@@ -6906,36 +6955,43 @@
|
|
|
6906
6955
|
// Summary and badge changes must fit on their first visible frame, including after resize.
|
|
6907
6956
|
const ring = this.options.anchorRing;
|
|
6908
6957
|
const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
|
|
6909
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
6910
|
-
const
|
|
6911
|
-
const
|
|
6958
|
+
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (_d = this.visualRegister) === null || _d === void 0 ? void 0 : _d.GetLabelHoverPosition((_e = this.rego) === null || _e === void 0 ? void 0 : _e.entityId, (_f = this.rego) === null || _f === void 0 ? void 0 : _f.menuItemId)) : undefined));
|
|
6959
|
+
const iconRing = frame.iconRadius ? Math.max(ICON_RING_MIN, frame.iconRadius + ICON_RING_PADDING) : 0;
|
|
6960
|
+
const hovered = pointer && Math.hypot(pointer.x - frame.anchorX, pointer.y - frame.anchorY) <= Math.max((_g = ring === null || ring === void 0 ? void 0 : ring.hoverDistance) !== null && _g !== void 0 ? _g : 24, iconRing);
|
|
6961
|
+
const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((_h = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _h !== void 0 ? _h : 100) ? ((_j = ring === null || ring === void 0 ? void 0 : ring.nearRadius) !== null && _j !== void 0 ? _j : 10) : 0, hovered ? ((_k = ring === null || ring === void 0 ? void 0 : ring.hoverRadius) !== null && _k !== void 0 ? _k : (this.Transient && !this.Persistent ? 15.4 : 22)) : 0));
|
|
6912
6962
|
const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
|
|
6963
|
+
// Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
|
|
6964
|
+
const targetOuter = iconRing || targetRadius || 2.5;
|
|
6965
|
+
const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
|
|
6913
6966
|
const now = Date.now();
|
|
6914
|
-
const duration = (
|
|
6967
|
+
const duration = (_l = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _l !== void 0 ? _l : 200;
|
|
6915
6968
|
const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
|
|
6916
6969
|
let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
|
|
6917
6970
|
if (!ringAnimation) {
|
|
6918
|
-
|
|
6971
|
+
const outer = iconRing || 2.5;
|
|
6972
|
+
const inner = iconRing;
|
|
6973
|
+
ringAnimation = { target: outer, targetInner: inner, start: now, outer, inner, fromOuter: outer, fromInner: inner };
|
|
6919
6974
|
}
|
|
6920
6975
|
const animateRing = () => {
|
|
6921
6976
|
const progress = duration > 0 ? Math.min(1, Math.max(0, (now - ringAnimation.start) / duration)) : 1;
|
|
6922
6977
|
const eased = progress * progress * (3 - 2 * progress);
|
|
6923
|
-
ringAnimation.outer = ringAnimation.fromOuter + (
|
|
6924
|
-
ringAnimation.inner = ringAnimation.fromInner + (
|
|
6978
|
+
ringAnimation.outer = ringAnimation.fromOuter + (ringAnimation.target - ringAnimation.fromOuter) * eased;
|
|
6979
|
+
ringAnimation.inner = ringAnimation.fromInner + (ringAnimation.targetInner - ringAnimation.fromInner) * eased;
|
|
6925
6980
|
return progress;
|
|
6926
6981
|
};
|
|
6927
6982
|
animateRing();
|
|
6928
|
-
if (ringAnimation.target !==
|
|
6983
|
+
if (ringAnimation.target !== targetOuter || ringAnimation.targetInner !== targetInner) {
|
|
6929
6984
|
ringAnimation = {
|
|
6930
6985
|
...ringAnimation,
|
|
6931
|
-
target:
|
|
6986
|
+
target: targetOuter,
|
|
6987
|
+
targetInner,
|
|
6932
6988
|
start: now,
|
|
6933
6989
|
fromOuter: ringAnimation.outer,
|
|
6934
6990
|
fromInner: ringAnimation.inner
|
|
6935
6991
|
};
|
|
6936
6992
|
}
|
|
6937
6993
|
// Animate the hole and stem together; no CSS transition may lag behind a moving world anchor.
|
|
6938
|
-
if (animateRing() < 1 && (ringAnimation.fromOuter !==
|
|
6994
|
+
if (animateRing() < 1 && (ringAnimation.fromOuter !== targetOuter || ringAnimation.fromInner !== targetInner)) {
|
|
6939
6995
|
this.viewer.scene.requestRender();
|
|
6940
6996
|
}
|
|
6941
6997
|
this.ringAnimation = ringAnimation;
|
|
@@ -6950,19 +7006,19 @@
|
|
|
6950
7006
|
if (leader && angled) {
|
|
6951
7007
|
// The declutter owns the side; the fit only says which one had the room to begin with.
|
|
6952
7008
|
const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
|
|
6953
|
-
const side = (
|
|
7009
|
+
const side = (_o = (_m = placement.textSide) !== null && _m !== void 0 ? _m : frame.leaderSide) !== null && _o !== void 0 ? _o : "right";
|
|
6954
7010
|
const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
|
|
6955
7011
|
position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
|
|
6956
7012
|
}
|
|
6957
7013
|
else if (leader && placement.textSide === "left") {
|
|
6958
7014
|
position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
|
|
6959
7015
|
}
|
|
6960
|
-
const gap = (
|
|
7016
|
+
const gap = (_q = (_p = this.options.pointerAvoidance) === null || _p === void 0 ? void 0 : _p.gap) !== null && _q !== void 0 ? _q : 12;
|
|
6961
7017
|
const containsPointer = (x, y, margin) => {
|
|
6962
7018
|
return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
|
|
6963
7019
|
};
|
|
6964
7020
|
const passive = !selectOnClick || !this.visualRegister;
|
|
6965
|
-
if (passive && ((
|
|
7021
|
+
if (passive && ((_r = this.options.pointerAvoidance) === null || _r === void 0 ? void 0 : _r.enabled) !== false && pointer &&
|
|
6966
7022
|
(containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
|
|
6967
7023
|
if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
|
|
6968
7024
|
// Choose against the original box and retain the offset until the pointer leaves both boxes.
|
|
@@ -6980,7 +7036,7 @@
|
|
|
6980
7036
|
else {
|
|
6981
7037
|
displacement = { x: 0, y: 0 };
|
|
6982
7038
|
}
|
|
6983
|
-
const nudgeDuration = (
|
|
7039
|
+
const nudgeDuration = (_t = (_s = this.options.pointerAvoidance) === null || _s === void 0 ? void 0 : _s.transitionDuration) !== null && _t !== void 0 ? _t : 200;
|
|
6984
7040
|
const animateNudge = () => {
|
|
6985
7041
|
const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
|
|
6986
7042
|
const eased = progress * progress * (3 - 2 * progress);
|
|
@@ -6996,7 +7052,7 @@
|
|
|
6996
7052
|
fromY: nudge.y,
|
|
6997
7053
|
targetX: displacement.x,
|
|
6998
7054
|
targetY: displacement.y,
|
|
6999
|
-
start: now + ((
|
|
7055
|
+
start: now + ((_v = (_u = this.options.pointerAvoidance) === null || _u === void 0 ? void 0 : _u.delay) !== null && _v !== void 0 ? _v : 180)
|
|
7000
7056
|
};
|
|
7001
7057
|
}
|
|
7002
7058
|
if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
|
|
@@ -7006,9 +7062,8 @@
|
|
|
7006
7062
|
position.y += nudge.y;
|
|
7007
7063
|
ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
|
|
7008
7064
|
ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
|
|
7009
|
-
const clickTarget
|
|
7010
|
-
|
|
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}`);
|
|
7065
|
+
for (const clickTarget of Array.from(content.querySelectorAll('[role="button"]'))) {
|
|
7066
|
+
clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_x = (_w = this.options.text) !== null && _w !== void 0 ? _w : text) !== null && _x !== void 0 ? _x : (_y = this.rego) === null || _y === void 0 ? void 0 : _y.entityId}`);
|
|
7012
7067
|
}
|
|
7013
7068
|
if (leader) {
|
|
7014
7069
|
const nodeRadius = ringAnimation.outer;
|
|
@@ -7048,13 +7103,13 @@
|
|
|
7048
7103
|
// Keep the stem inside the 126-degree arc, away from antialiased endpoints.
|
|
7049
7104
|
const arcFraction = 0.35;
|
|
7050
7105
|
const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
|
|
7051
|
-
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((
|
|
7106
|
+
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_z = ringAnimation.arcOffset) !== null && _z !== void 0 ? _z : 0);
|
|
7052
7107
|
const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
|
|
7053
|
-
const elapsed = Math.min(32, Math.max(0, now - ((
|
|
7108
|
+
const elapsed = Math.min(32, Math.max(0, now - ((_0 = ringAnimation.arcTime) !== null && _0 !== void 0 ? _0 : now)));
|
|
7054
7109
|
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
7055
7110
|
const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
|
|
7056
|
-
ringAnimation.arcOffset = approach((
|
|
7057
|
-
ringAnimation.arcOpen = approach((
|
|
7111
|
+
ringAnimation.arcOffset = approach((_1 = ringAnimation.arcOffset) !== null && _1 !== void 0 ? _1 : 0, targetOffset);
|
|
7112
|
+
ringAnimation.arcOpen = approach((_2 = ringAnimation.arcOpen) !== null && _2 !== void 0 ? _2 : 0, targetOpen);
|
|
7058
7113
|
ringAnimation.arcTime = now;
|
|
7059
7114
|
if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
|
|
7060
7115
|
this.viewer.scene.requestRender();
|
|
@@ -7107,6 +7162,123 @@
|
|
|
7107
7162
|
}, 2500);
|
|
7108
7163
|
});
|
|
7109
7164
|
}
|
|
7165
|
+
/*
|
|
7166
|
+
* Builds one icon glyph from label options, with its background chip when asked for.
|
|
7167
|
+
*
|
|
7168
|
+
* The returned fit squares the chip off once the glyph has a measurable size.
|
|
7169
|
+
*/
|
|
7170
|
+
buildIcon(options, counter) {
|
|
7171
|
+
var _a, _b, _c;
|
|
7172
|
+
let fit;
|
|
7173
|
+
const icon = document.createElement("span");
|
|
7174
|
+
icon.className = "entity-label-icon";
|
|
7175
|
+
icon.style.cssText = `font-size:${(options.iconSize || 24) * ((_a = options.iconScale) !== null && _a !== void 0 ? _a : 1)}px;line-height:1;`;
|
|
7176
|
+
icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
|
|
7177
|
+
const iconColor = typeof options.iconColor === "string" ? options.iconColor : undefined;
|
|
7178
|
+
const iconBackground = options.iconBackground && options.iconScale !== 0;
|
|
7179
|
+
if (iconColor) {
|
|
7180
|
+
icon.style.color = iconColor;
|
|
7181
|
+
}
|
|
7182
|
+
if (options.iconUrl) {
|
|
7183
|
+
const image = document.createElement("img");
|
|
7184
|
+
image.draggable = false;
|
|
7185
|
+
image.alt = options.icon || "";
|
|
7186
|
+
const scale = (_b = options.iconScale) !== null && _b !== void 0 ? _b : 1;
|
|
7187
|
+
const size = options.iconSize;
|
|
7188
|
+
const svgNs = "http://www.w3.org/2000/svg";
|
|
7189
|
+
const tint = iconColor ? document.createElementNS(svgNs, "svg") : null;
|
|
7190
|
+
const painted = tint ? document.createElementNS(svgNs, "image") : null;
|
|
7191
|
+
const inked = tint ? document.createElementNS(svgNs, "rect") : null;
|
|
7192
|
+
if (tint) {
|
|
7193
|
+
const maskId = `entity-label-tint-${++tintCounter}`;
|
|
7194
|
+
const mask = document.createElementNS(svgNs, "mask");
|
|
7195
|
+
mask.setAttribute("id", maskId);
|
|
7196
|
+
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
|
7197
|
+
painted.setAttribute("href", options.iconUrl);
|
|
7198
|
+
painted.setAttribute("preserveAspectRatio", "xMidYMid meet");
|
|
7199
|
+
mask.appendChild(painted);
|
|
7200
|
+
inked.setAttribute("fill", iconColor);
|
|
7201
|
+
inked.setAttribute("mask", `url(#${maskId})`);
|
|
7202
|
+
tint.append(mask, inked);
|
|
7203
|
+
tint.style.display = "block";
|
|
7204
|
+
icon.style.display = "inline-flex";
|
|
7205
|
+
icon.appendChild(tint);
|
|
7206
|
+
}
|
|
7207
|
+
const resize = () => {
|
|
7208
|
+
if (this.disposed || this.counter !== counter) {
|
|
7209
|
+
return;
|
|
7210
|
+
}
|
|
7211
|
+
const intrinsic = options.iconScale != null && size == null;
|
|
7212
|
+
const width = (intrinsic ? image.naturalWidth || 24 : size || 24) * scale;
|
|
7213
|
+
const height = (intrinsic ? image.naturalHeight || 24 : size || 24) * scale;
|
|
7214
|
+
if (tint) {
|
|
7215
|
+
tint.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
7216
|
+
for (const node of [tint, painted, inked]) {
|
|
7217
|
+
node.setAttribute("width", String(width));
|
|
7218
|
+
node.setAttribute("height", String(height));
|
|
7219
|
+
}
|
|
7220
|
+
}
|
|
7221
|
+
else {
|
|
7222
|
+
image.style.width = `${width}px`;
|
|
7223
|
+
image.style.height = `${height}px`;
|
|
7224
|
+
}
|
|
7225
|
+
this.viewer.scene.requestRender();
|
|
7226
|
+
};
|
|
7227
|
+
image.onload = resize;
|
|
7228
|
+
resize();
|
|
7229
|
+
image.src = options.iconUrl;
|
|
7230
|
+
if (!tint) {
|
|
7231
|
+
icon.appendChild(image);
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7234
|
+
else if (typeof options.iconClass === "string" && options.iconClass) {
|
|
7235
|
+
icon.className = `entity-label-icon ${options.iconClass}`;
|
|
7236
|
+
}
|
|
7237
|
+
else {
|
|
7238
|
+
icon.textContent = options.icon || "●";
|
|
7239
|
+
}
|
|
7240
|
+
if (iconBackground) {
|
|
7241
|
+
const color = Cesium.Color.fromCssColorString(iconColor || "white") || Cesium.Color.WHITE;
|
|
7242
|
+
const luminance = 0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue;
|
|
7243
|
+
const background = (luminance > 0.5 ? color.darken(ICON_BACKGROUND_BLEND, new Cesium.Color()) :
|
|
7244
|
+
color.brighten(ICON_BACKGROUND_BLEND, new Cesium.Color()));
|
|
7245
|
+
const size = (options.iconSize || 24) * ((_c = options.iconScale) !== null && _c !== void 0 ? _c : 1);
|
|
7246
|
+
icon.style.backgroundColor = background.withAlpha(ICON_BACKGROUND_ALPHA).toCssColorString();
|
|
7247
|
+
icon.style.borderRadius = "50%";
|
|
7248
|
+
icon.style.padding = `${size / 3}px`;
|
|
7249
|
+
icon.style.display = "inline-flex";
|
|
7250
|
+
icon.style.alignItems = "center";
|
|
7251
|
+
icon.style.justifyContent = "center";
|
|
7252
|
+
icon.style.minWidth = `${size}px`;
|
|
7253
|
+
icon.style.minHeight = `${size}px`;
|
|
7254
|
+
icon.style.boxSizing = "content-box";
|
|
7255
|
+
let fitted = false;
|
|
7256
|
+
fit = () => {
|
|
7257
|
+
if (fitted) {
|
|
7258
|
+
return;
|
|
7259
|
+
}
|
|
7260
|
+
const padding = size / 3;
|
|
7261
|
+
const width = icon.offsetWidth - padding * 2;
|
|
7262
|
+
const height = icon.offsetHeight - padding * 2;
|
|
7263
|
+
if (width <= 0 || height <= 0) {
|
|
7264
|
+
return;
|
|
7265
|
+
}
|
|
7266
|
+
fitted = true;
|
|
7267
|
+
const longest = Math.max(width, height);
|
|
7268
|
+
const difference = (longest - Math.min(width, height)) / longest;
|
|
7269
|
+
if (difference > ICON_BACKGROUND_UNIFORM_TOLERANCE) {
|
|
7270
|
+
return;
|
|
7271
|
+
}
|
|
7272
|
+
icon.style.minWidth = `${longest}px`;
|
|
7273
|
+
icon.style.minHeight = `${longest}px`;
|
|
7274
|
+
this.viewer.scene.requestRender();
|
|
7275
|
+
};
|
|
7276
|
+
}
|
|
7277
|
+
if (options.iconScale === 0) {
|
|
7278
|
+
icon.style.display = "none";
|
|
7279
|
+
}
|
|
7280
|
+
return { icon, fit };
|
|
7281
|
+
}
|
|
7110
7282
|
/**
|
|
7111
7283
|
* Removes label from the viewer and unsubscribes from events.
|
|
7112
7284
|
*/
|
|
@@ -16754,9 +16926,6 @@
|
|
|
16754
16926
|
// Last date-time range taken from the clock.
|
|
16755
16927
|
this.lastDateTimeMin = null;
|
|
16756
16928
|
this.lastDateTimeMax = null;
|
|
16757
|
-
// Entity ID -> last resolved date time.
|
|
16758
|
-
// Helps determine if a slow resolved request can still be applied while we wait for the next one.
|
|
16759
|
-
this.lastFoundDateTimes = new Map();
|
|
16760
16929
|
// ID of Entity IDs that need to be requested.
|
|
16761
16930
|
this.eIdQueue = [];
|
|
16762
16931
|
// Membership mirror of eIdQueue for quick checks.
|
|
@@ -16788,8 +16957,12 @@
|
|
|
16788
16957
|
// Unsubscribes the view monitor listener that queues what the camera has moved onto.
|
|
16789
16958
|
this.monitorRemoval = null;
|
|
16790
16959
|
// Indicates a tick is active.
|
|
16791
|
-
// When true,
|
|
16960
|
+
// When true, a new tick is not started; it is noted in pendingTick and run once this one ends.
|
|
16792
16961
|
this.runningTick = false;
|
|
16962
|
+
// A tick was asked for while one was running, so another is owed once it finishes.
|
|
16963
|
+
this.pendingTick = false;
|
|
16964
|
+
// The debounce every clock change goes through, kept so a tick owed after a running one does too.
|
|
16965
|
+
this.tickDelay = null;
|
|
16793
16966
|
// Entity ID -> Entity blob.
|
|
16794
16967
|
// This is a tool that lets developers set their own JSON blobs instead of requesting data from API.
|
|
16795
16968
|
this.entityDataOverrides = new Map();
|
|
@@ -16853,11 +17026,13 @@
|
|
|
16853
17026
|
let tickDelay = new BModels.DelayQueue(() => {
|
|
16854
17027
|
this.onTick(false);
|
|
16855
17028
|
}, 250, true);
|
|
17029
|
+
this.tickDelay = tickDelay;
|
|
16856
17030
|
this.hDisposals.push(() => {
|
|
16857
17031
|
if (tickDelay) {
|
|
16858
17032
|
tickDelay.Dispose();
|
|
16859
17033
|
}
|
|
16860
17034
|
tickDelay = null;
|
|
17035
|
+
this.tickDelay = null;
|
|
16861
17036
|
});
|
|
16862
17037
|
// React to clock changes and request new Entities.
|
|
16863
17038
|
let prevTime = null;
|
|
@@ -16897,7 +17072,6 @@
|
|
|
16897
17072
|
this.priorityCount = 0;
|
|
16898
17073
|
this.lastOrderFocus = null;
|
|
16899
17074
|
this.allEIds.clear();
|
|
16900
|
-
this.lastFoundDateTimes.clear();
|
|
16901
17075
|
this.everFound.clear();
|
|
16902
17076
|
this.clearIndex();
|
|
16903
17077
|
this.lastDateTime = null;
|
|
@@ -17259,7 +17433,6 @@
|
|
|
17259
17433
|
this.eIdQueued.clear();
|
|
17260
17434
|
this.priorityCount = 0;
|
|
17261
17435
|
this.allEIds.clear();
|
|
17262
|
-
this.lastFoundDateTimes.clear();
|
|
17263
17436
|
this.everFound.clear();
|
|
17264
17437
|
this.clearIndex();
|
|
17265
17438
|
this.entityDataOverrides.clear();
|
|
@@ -17275,7 +17448,13 @@
|
|
|
17275
17448
|
}
|
|
17276
17449
|
}
|
|
17277
17450
|
onTick(force) {
|
|
17278
|
-
if (this.
|
|
17451
|
+
if (this.disposed) {
|
|
17452
|
+
return;
|
|
17453
|
+
}
|
|
17454
|
+
if (this.runningTick) {
|
|
17455
|
+
if (this.historic) {
|
|
17456
|
+
this.pendingTick = true;
|
|
17457
|
+
}
|
|
17279
17458
|
return;
|
|
17280
17459
|
}
|
|
17281
17460
|
const rTime = this.viewer.clock.currentTime.toString();
|
|
@@ -17283,10 +17462,16 @@
|
|
|
17283
17462
|
return;
|
|
17284
17463
|
}
|
|
17285
17464
|
this.lastDateTime = rTime;
|
|
17465
|
+
// Whether the clock has left the moment this tick is reading.
|
|
17466
|
+
const movedOn = () => this.historic && this.viewer.clock.currentTime.toString() !== rTime;
|
|
17286
17467
|
(async () => {
|
|
17287
17468
|
this.runningTick = true;
|
|
17469
|
+
this.pendingTick = false;
|
|
17288
17470
|
this.placeStragglers();
|
|
17289
17471
|
let rerun = false;
|
|
17472
|
+
let interrupted = false;
|
|
17473
|
+
// Batch's answer is applied while the next batch is being read.
|
|
17474
|
+
const applying = [];
|
|
17290
17475
|
try {
|
|
17291
17476
|
const handleResponse = async (requestedBatch, entities) => {
|
|
17292
17477
|
var _a, _b, _c, _d;
|
|
@@ -17350,45 +17535,7 @@
|
|
|
17350
17535
|
api: this.api
|
|
17351
17536
|
})).tags;
|
|
17352
17537
|
}
|
|
17353
|
-
|
|
17354
|
-
// 1 = rTime is in the future.
|
|
17355
|
-
// -1 = rTime is in the past.
|
|
17356
|
-
let changeDir = 0;
|
|
17357
|
-
if (this.lastDateTime !== this.viewer.clock.currentTime.toString()) {
|
|
17358
|
-
changeDir = this.lastDateTime < this.viewer.clock.currentTime.toString() ? 1 : -1;
|
|
17359
|
-
}
|
|
17360
|
-
let toEmit;
|
|
17361
|
-
if (this.historic) {
|
|
17362
|
-
// We can emit Entities if the last resolved time (per Entity) isn't newer than rTime.
|
|
17363
|
-
// We compare using the changeDir to determine if we should emit or not.
|
|
17364
|
-
toEmit = resolvedEntities.filter((e) => {
|
|
17365
|
-
var _a;
|
|
17366
|
-
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);
|
|
17367
|
-
const lastFoundDateTime = this.lastFoundDateTimes.get(entityId);
|
|
17368
|
-
if (lastFoundDateTime) {
|
|
17369
|
-
if (changeDir === 1) {
|
|
17370
|
-
return lastFoundDateTime <= rTime;
|
|
17371
|
-
}
|
|
17372
|
-
else if (changeDir === -1) {
|
|
17373
|
-
return lastFoundDateTime >= rTime;
|
|
17374
|
-
}
|
|
17375
|
-
}
|
|
17376
|
-
return true;
|
|
17377
|
-
});
|
|
17378
|
-
// Update the last found date times for the filtered Entities.
|
|
17379
|
-
toEmit.forEach((e) => {
|
|
17380
|
-
var _a;
|
|
17381
|
-
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);
|
|
17382
|
-
if (entityId) {
|
|
17383
|
-
this.lastFoundDateTimes.set(entityId, rTime);
|
|
17384
|
-
}
|
|
17385
|
-
});
|
|
17386
|
-
}
|
|
17387
|
-
else {
|
|
17388
|
-
// If we're not historic, we can just emit everything.
|
|
17389
|
-
toEmit = resolvedEntities;
|
|
17390
|
-
}
|
|
17391
|
-
this.emitEntities(toEmit, tags);
|
|
17538
|
+
this.emitEntities(resolvedEntities, tags);
|
|
17392
17539
|
};
|
|
17393
17540
|
const QUEUE_BATCH_SIZE = 500;
|
|
17394
17541
|
const requestedIds = new Set();
|
|
@@ -17397,7 +17544,8 @@
|
|
|
17397
17544
|
const total = this.eIdQueue.length;
|
|
17398
17545
|
let done = 0;
|
|
17399
17546
|
while (this.eIdQueue.length && !this.disposed) {
|
|
17400
|
-
if (
|
|
17547
|
+
if (movedOn()) {
|
|
17548
|
+
interrupted = true;
|
|
17401
17549
|
break;
|
|
17402
17550
|
}
|
|
17403
17551
|
this.orderQueueByCamera();
|
|
@@ -17413,7 +17561,7 @@
|
|
|
17413
17561
|
api: this.api,
|
|
17414
17562
|
maxSearchTimeSec: 60 * 2
|
|
17415
17563
|
});
|
|
17416
|
-
handleResponse(batch, entities);
|
|
17564
|
+
applying.push(handleResponse(batch, entities));
|
|
17417
17565
|
for (let i = 0; i < batch.length; i++) {
|
|
17418
17566
|
requestedIds.add(batch[i]);
|
|
17419
17567
|
}
|
|
@@ -17445,7 +17593,8 @@
|
|
|
17445
17593
|
// The near ones are resolved first out of whatever the view still reaches.
|
|
17446
17594
|
allEIds = this.bandIds(allEIds);
|
|
17447
17595
|
while (allEIds.length && !this.disposed && !this.eIdQueue.length) {
|
|
17448
|
-
if (
|
|
17596
|
+
if (movedOn()) {
|
|
17597
|
+
interrupted = true;
|
|
17449
17598
|
break;
|
|
17450
17599
|
}
|
|
17451
17600
|
const batch = allEIds.splice(0, QUEUE_BATCH_SIZE);
|
|
@@ -17456,7 +17605,7 @@
|
|
|
17456
17605
|
api: this.api,
|
|
17457
17606
|
maxSearchTimeSec: 60 * 2
|
|
17458
17607
|
});
|
|
17459
|
-
handleResponse(batch, entities);
|
|
17608
|
+
applying.push(handleResponse(batch, entities));
|
|
17460
17609
|
for (let i = 0; i < batch.length; i++) {
|
|
17461
17610
|
requestedIds.add(batch[i]);
|
|
17462
17611
|
}
|
|
@@ -17471,10 +17620,32 @@
|
|
|
17471
17620
|
console.error(e);
|
|
17472
17621
|
}
|
|
17473
17622
|
finally {
|
|
17623
|
+
// Only a historic answer can be overtaken by a newer moment, so nothing else waits.
|
|
17624
|
+
if (this.historic && applying.length) {
|
|
17625
|
+
await Promise.all(applying.map((pending) => pending.catch((e) => console.error(e))));
|
|
17626
|
+
}
|
|
17474
17627
|
this.runningTick = false;
|
|
17475
17628
|
}
|
|
17476
|
-
|
|
17629
|
+
// A moment read only in part is forgotten, so coming back to it reads it again in full.
|
|
17630
|
+
if (interrupted && this.lastDateTime === rTime) {
|
|
17631
|
+
this.lastDateTime = null;
|
|
17632
|
+
}
|
|
17633
|
+
if (this.disposed) {
|
|
17634
|
+
return;
|
|
17635
|
+
}
|
|
17636
|
+
// Leftovers at the same moment carry straight on, as they always have.
|
|
17637
|
+
if (rerun && !interrupted) {
|
|
17477
17638
|
this.onTick(true);
|
|
17639
|
+
return;
|
|
17640
|
+
}
|
|
17641
|
+
if (interrupted || this.pendingTick || movedOn()) {
|
|
17642
|
+
this.pendingTick = false;
|
|
17643
|
+
if (this.tickDelay) {
|
|
17644
|
+
this.tickDelay.Call();
|
|
17645
|
+
}
|
|
17646
|
+
else {
|
|
17647
|
+
this.onTick(false);
|
|
17648
|
+
}
|
|
17478
17649
|
}
|
|
17479
17650
|
})();
|
|
17480
17651
|
}
|
|
@@ -49069,7 +49240,7 @@ void main() {
|
|
|
49069
49240
|
}
|
|
49070
49241
|
EntityRenderEngine.GetGraphicShapeId = GetGraphicShapeId;
|
|
49071
49242
|
async function Render(params) {
|
|
49072
|
-
var _a, _b, _c;
|
|
49243
|
+
var _a, _b, _c, _d, _e;
|
|
49073
49244
|
const groupRenderParams = {
|
|
49074
49245
|
apiGetter: params.apiGetter,
|
|
49075
49246
|
viewer: params.viewer,
|
|
@@ -49246,10 +49417,20 @@ void main() {
|
|
|
49246
49417
|
polygons.push(entity);
|
|
49247
49418
|
continue;
|
|
49248
49419
|
}
|
|
49420
|
+
// Present when the gate above chose to reuse this entity's graphic.
|
|
49421
|
+
const existingRoot = cEntities.get(entity.Bruce.ID);
|
|
49422
|
+
const existingParts = new Map();
|
|
49423
|
+
(_d = existingRoot === null || existingRoot === void 0 ? void 0 : existingRoot._siblingGraphics) === null || _d === void 0 ? void 0 : _d.forEach((part) => {
|
|
49424
|
+
if (part._multiPartIndex != null) {
|
|
49425
|
+
existingParts.set(part._multiPartIndex, part);
|
|
49426
|
+
}
|
|
49427
|
+
});
|
|
49428
|
+
const partRendered = new Map();
|
|
49429
|
+
const partIndexes = new Map();
|
|
49249
49430
|
const pParams = {
|
|
49250
49431
|
...groupRenderParams,
|
|
49251
49432
|
entities: [],
|
|
49252
|
-
rendered:
|
|
49433
|
+
rendered: partRendered
|
|
49253
49434
|
};
|
|
49254
49435
|
pParams.entitiesHistoric = params.entitiesHistoric;
|
|
49255
49436
|
const zoomItem = pParams.zoomItems[entity.Bruce.ID];
|
|
@@ -49265,39 +49446,59 @@ void main() {
|
|
|
49265
49446
|
};
|
|
49266
49447
|
pParams.zoomItems[subEntity.Bruce.ID] = zoomItem;
|
|
49267
49448
|
pParams.entities.push(subEntity);
|
|
49268
|
-
|
|
49269
|
-
|
|
49270
|
-
|
|
49271
|
-
|
|
49272
|
-
|
|
49273
|
-
|
|
49274
|
-
|
|
49275
|
-
|
|
49276
|
-
|
|
49277
|
-
|
|
49278
|
-
|
|
49449
|
+
partIndexes.set(subEntity.Bruce.ID, j);
|
|
49450
|
+
const existingPart = existingParts.get(j);
|
|
49451
|
+
if (existingPart) {
|
|
49452
|
+
// An engine preparing a reused graphic gathers everything connected to it, so a part still
|
|
49453
|
+
// attached to its root would have every other part evicted from the scene.
|
|
49454
|
+
existingPart._parentEntity = null;
|
|
49455
|
+
partRendered.set(subEntity.Bruce.ID, existingPart);
|
|
49456
|
+
}
|
|
49457
|
+
}
|
|
49458
|
+
const rendered = [];
|
|
49459
|
+
const collect = (results) => {
|
|
49460
|
+
results.forEach((cEntity, key) => {
|
|
49461
|
+
if (!cEntity) {
|
|
49462
|
+
return;
|
|
49463
|
+
}
|
|
49464
|
+
cEntity._multiPartIndex = partIndexes.get(key);
|
|
49465
|
+
rendered.push(cEntity);
|
|
49279
49466
|
pParams.entities = pParams.entities.filter((e) => e.Bruce.ID != key);
|
|
49280
|
-
}
|
|
49281
|
-
}
|
|
49282
|
-
|
|
49467
|
+
});
|
|
49468
|
+
};
|
|
49469
|
+
collect(await Polygon.RenderGroup(pParams));
|
|
49470
|
+
collect(await Polyline.RenderGroup(pParams));
|
|
49283
49471
|
// We'll only render points when there's no other geometry.
|
|
49284
49472
|
// Generally speaking when polygons/polylines are drawn, points are only drawn because of invalid data.
|
|
49285
49473
|
if (!rendered.length) {
|
|
49286
|
-
|
|
49287
|
-
rendered = rendered.concat(Array.from(cPoints.values()));
|
|
49474
|
+
collect(await Point.RenderGroup(pParams));
|
|
49288
49475
|
}
|
|
49289
|
-
rendered = rendered.filter(x => x != null);
|
|
49290
49476
|
if (rendered.length) {
|
|
49291
|
-
const
|
|
49292
|
-
|
|
49293
|
-
|
|
49294
|
-
|
|
49477
|
+
const sameParts = ((_e = existingRoot === null || existingRoot === void 0 ? void 0 : existingRoot._siblingGraphics) === null || _e === void 0 ? void 0 : _e.length) == rendered.length &&
|
|
49478
|
+
rendered.every((part) => existingRoot._siblingGraphics.includes(part));
|
|
49479
|
+
let rootEntity;
|
|
49480
|
+
if (sameParts) {
|
|
49481
|
+
rootEntity = existingRoot;
|
|
49482
|
+
}
|
|
49483
|
+
else {
|
|
49484
|
+
if (existingRoot === null || existingRoot === void 0 ? void 0 : existingRoot._siblingGraphics) {
|
|
49485
|
+
// Retiring the old root removes its parts, and the ones carried over belong to the new root now.
|
|
49486
|
+
existingRoot._siblingGraphics = existingRoot._siblingGraphics.filter((part) => !rendered.includes(part));
|
|
49487
|
+
}
|
|
49488
|
+
rootEntity = new Cesium.Entity({
|
|
49489
|
+
id: BModels.ObjectUtils.UId(15)
|
|
49490
|
+
});
|
|
49491
|
+
}
|
|
49492
|
+
rootEntity._siblingGraphics = rendered;
|
|
49295
49493
|
rootEntity._renderGroup = GetRenderGroupId(zoomItem);
|
|
49296
|
-
|
|
49494
|
+
// The skip gate compares the rego's style against the zoom item's, and the rego takes it from here.
|
|
49495
|
+
rootEntity.styleId = (zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) == -1 ? -1 : (+(zoomItem === null || zoomItem === void 0 ? void 0 : zoomItem.StyleID) || null);
|
|
49496
|
+
rootEntity.styleEffective = rendered[0].styleEffective;
|
|
49297
49497
|
cEntities.set(entity.Bruce.ID, rootEntity);
|
|
49298
|
-
const
|
|
49299
|
-
|
|
49300
|
-
|
|
49498
|
+
const newPosition = GetCValue(params.viewer, rendered[0].position);
|
|
49499
|
+
const oldPosition = GetCValue(params.viewer, rootEntity.position);
|
|
49500
|
+
if (newPosition && (!oldPosition || !BModels.Cartes.IsEqualCartes3(oldPosition, newPosition))) {
|
|
49501
|
+
rootEntity.position = new Cesium.ConstantPositionProperty(newPosition);
|
|
49301
49502
|
}
|
|
49302
49503
|
rendered.forEach((child) => {
|
|
49303
49504
|
child._parentEntity = rootEntity;
|
|
@@ -50231,7 +50432,7 @@ void main() {
|
|
|
50231
50432
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
50232
50433
|
})(exports.StyleUtils || (exports.StyleUtils = {}));
|
|
50233
50434
|
|
|
50234
|
-
const VERSION = "7.4.
|
|
50435
|
+
const VERSION = "7.4.4";
|
|
50235
50436
|
/**
|
|
50236
50437
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
50237
50438
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|